java线程池

news/2024/11/7 10:46:51/

一、线程池的好处

1、为什么需要使用线程池

  • 反复创建线程开销大 (内存和垃圾回收)
  • 过多的线程会导致内存消耗
  • 线程池可以规避上述问题,它可以复用每一个线程,并且还可以控制线程总量,便于管理

3、不使用线程池会怎么样

  • 每次创建和销毁线程都需要资源的开销,会有很大部分的资源浪费
  • 任务数量过多时,新建线程过多可能会OOM异常,即内存溢出

5、线程池的优点

  • 加快响应速度
  • 便于线程的管理控制,方便数据统计
  • 限制线程资源的总量,合理利用CPU和内存

6、线程池适合的场合

  • 服务器接受到大量请求时,使用线程池技术是非常合适的,它可以大大减少线程的创建和销毁次数,提高服务器的工作效率;
  • 实际上,在开发中,如果需要创建5个以上的线程,那么就可以使用线程池来管理

二、创建和停止线程池

1、线程池构造函数的参数

参数类型含义
corePoolSizeint核心线程数
maxPoolSizeint最大线程数
keepAliveTimelong保持存活时间
workQueueBlockingQueue任务存储队列
threadFactoryThreadFactory当线程池需要新的线程的时候,会使用threadFactory来生成新的线程
HandlerRejectedExecutionHandler由于线程池无法接受你所提交的任务的拒绝策略

(1)corePoolSize

核心线程池大小, 线程池初始化后,默认是没有任何线程的,当新的任务到线程池后,线程池会创建新的线程(即使有空闲线程),直到核心线程池已满。

(2) maxPoolSize:

最大线程池大小,顾名思义,线程池能创建的线程的最大数目.

什么情况下线程数量会超过 corePoolSize ,增加到 maxPoolSize ?

  • 新任务到来时,如果线程数小于corePoolSize ,即使其他工作线程处于空闲状态,也会创建一个新线程来运行新任务;

  • 如果线程数等于或大于corePoolSize 但是少于maxPoolSize,则将任务放入workQueue队列(指定队列的容量);

  • 如果队列已满,并且线程数小于maxPoolSize,则创建一个新线程来运行任务;

  • 如果队列已满,并在线程数等于或大于maxPoolSize,则执行Handler拒绝策略;

    总之,是否需要增加线程的判断顺序是:corePoolSize -》workQueue-》maxPoolSize

线程池的线程数量增减的特点

  • 通过corePoolSize 和maxPoolSize相同,就可以创建固定大小的线程池;
  • 线程池希望保持较少的线程数,并且只有在负载变得很大的时候才增加它;
  • 通过设置maxPoolSize为很高的值,可以允许线程池容纳任意数量的并发任务;
  • 只有在队列填满时才多于corePoolSize 的线程,所以如果你使用的是无界队列,那么线程数就不会超过corePoolSize ;

(3)keepAliveTime:

如果线程池当前的线程数多于corePoolSize ,其中多余的那部分线程空闲时间超过keepAliveTime,它们就会被终止,比如:corePoolSize 为5,当前线程数为10,如果线程空闲了,就会恢复到5;

(4) ThreadFactory:

用来创建线程,默认是非守护线程,优先级是5,一共有10个等级;

(5) workQueue:

一个阻塞队列,用来存储等待执行的任务,有3种最常见的队列类型:

  • 直接交接:SynchronousQueue,一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
  • 无界队列:LinkedBlockingQueue,一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法 Executors.newFixedThreadPool() 使用了这个队列。队列的容量没有上限,有可能造成OOM。
  • 有界队列:ArrayBlockingQueue,是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。

(6) RejectedExecutionHandler

当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。有下面四种JDK提供的策略:

  • AbortPolicy,表示无法处理新任务时抛出异常, 默认策略
  • CallerRunsPolicy:用调用者所在线程来运行任务,即谁给的任务谁来执行
  • DiscardOldestPolicy: 该策略将丢弃最老的一个请求,也就是即将被执行的任务,并尝试再次提交当前任务。
  • DiscardPolicy:不处理,丢弃掉

除了这些JDK提供的策略外,还可以自己实现 RejectedExecutionHandler 接口定义策略。

2、线程池创建

Excutors类提供的创建线程池的方法如下:

(1) newFixedThreadPool

创建固定大小的线程池,核心线程数和最大线程数相等。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。
线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

  • 源码:
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(),threadFactory);
  • 使用演示:
/***          演示newFixedThreadPool*/
public class FixedThreadPoolTest {//主函数public static void main(String[] args) {ExecutorService threadPool = Executors.newFixedThreadPool(4);//执行线程池for (int i = 0; i < 1000; i++) {threadPool.execute(new Task());}}//任务类static class Task implements Runnable {@Overridepublic void run() {try {Thread.sleep(500);System.out.println(Thread.currentThread().getName());} catch (InterruptedException e) {e.printStackTrace();}}}
}

打印结果:

发现提交1000个任务,却只有4个线程在执行任务,跟我们设定4个线程执行要求是一致的

  • 错误演示

当因为任务过多无法处理导致内存资源耗尽

/***      演示newFixedThreadPool出错的情况*/
public class FixedThreadPoolOOM {//线程池private static ExecutorService executorService = Executors.newFixedThreadPool(1);public static void main(String[] args) {for (int i = 0; i < Integer.MAX_VALUE; i++) {//执行任务executorService.execute(new SubThread());}}
}//任务类
class SubThread implements Runnable{@Overridepublic void run() {try {Thread.sleep(1000000000);} catch (InterruptedException e) {e.printStackTrace();}}
}

为了更块地看到内存溢出,我们可以主动设置jvm参数,如下

最后出现了内存溢出OOM

由于使用的 LinkedBlockingQueue是没有容量上限的,所以当请求数越来越多,并且无法及时处理完毕的时候,也就是请求堆积的时候,会容易造成占用大量的内存,可能会导致OOM。

(2) newSingleThreadExecutor

创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。

  • 源码
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(),threadFactory));}

从源码可以看出,这里和刚才的newFixedThreadPool的原理基本一样,只不过把线程数直接设置成了1,所以这也会导致同样的问题,也就是当请求堆积的时候,可能会占用大量的内存。

  • 使用演示
public class SingleThreadExecutor {public static void main(String[] args) {ExecutorService executorService = Executors.newSingleThreadExecutor();for (int i = 0; i < 1000; i++) {executorService.execute(new Task());}}
}class Task implements Runnable{@Overridepublic void run() {try {Thread.sleep(500);System.out.println(Thread.currentThread().getName());} catch (InterruptedException e) {e.printStackTrace();}}
}

打印结果如下,始终只有一个线程名字,说明只有一个线程:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vmwxfRkR-1686042155717)(null)]

(3) newCachedThreadPool

创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。
此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小, 极端情况下会因为创建过多线程而耗尽系统资源,引发OOM

  • 特点:一个采用SynchronousQueue的无界限线程池,具有60s自动回收多余线程的功能
  • 源码:
public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());}
  • 使用演示
public class CachedThreadPool {public static void main(String[] args) {ExecutorService pool = Executors.newCachedThreadPool();for (int i = 0; i < 1000; i++) {pool.execute(new Task());}}static class Task implements Runnable{@Overridepublic void run() {try {Thread.sleep(500);System.out.println(Thread.currentThread().getName());} catch (InterruptedException e) {e.printStackTrace();}}}
}

打印结果

可以看到,每一个新任务,都会创建一个线程,他创建了1000个线程

(4)newScheduledThreadPool

  • 主要用来在给定的延迟之后运行任务,或者定期执行任务。
  • 源码
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory) {return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);}
  • 使用演示
public class ScheduleThreadPool {public static void main(String[] args) {ScheduledExecutorService pool = Executors.newScheduledThreadPool(10);//5秒钟执行任务,不重复执行
//        pool.schedule(new Task(),5, TimeUnit.SECONDS);//一开始1后执行任务,后面每隔3秒执行pool.scheduleAtFixedRate(new Task(),1,3,TimeUnit.SECONDS);}
}

打印结果:

由于设置了 corePoolSize=10,所以会出现10个线程,这10个线程执行的是同一个任务:一开始1s后执行任务,后面每隔3秒执行一次

(5)newWorkStealingPool

newWorkStealingPool 是jdk1.8才有的,会根据所需的并行层次来动态创建和关闭线程,通过使用多个队列减少竞争,底层用的 ForkJoinPool来实现的。ForkJoinPool 的优势在于,可以充分利用多cpu,多核cpu的优势,把一个任务拆分成多个“小任务”,把多个“小任务”放到多个处理器核心上并行执行;当多个“小任务”执行完成之后,再将这些执行结果合并起来即可。

五种线程池的使用场景

  • newSingleThreadExecutor:一个单线程的线程池,可以用于需要保证顺序执行的场景,并且只有一个线程在执行。
  • newFixedThreadPool:一个固定大小的线程池,可以用于已知并发压力的情况下,对线程数做限制。
  • newCachedThreadPool:一个可以无限扩大的线程池,比较适合处理执行时间比较小的任务。
  • newScheduledThreadPool:可以延时启动,定时启动的线程池,适用于需要多个后台线程执行周期任务的场景。
  • newWorkStealingPool:一个拥有多个任务队列的线程池,可以减少连接数,创建当前可用cpu数量的线程来并行执行。

以上都是使用 Executors 提供的方法创建线程池,不过更建议直接使用ThreadPoolExecutor 创建线程池,这样我们就能根据不同的业务场景,自己设置线程池参数,比如我们的内存有多大,我们想给线程取什么名字等等

3、线程池设定线程数量如何选择?

  • CPU密集型:比如八核处理器,那可以把corePoolSize 设置为核心数的1-2倍;
  • 耗时IO型(读写文件):这种类型的CPU是不工作的,可以设置为核心数的很多倍
  • 线程数=CPU核心数*(1+平均等待时间/平均工作时间)

4、停止线程的正确方法

(1) shutdown:

初始化整个关闭过程,不是直接关闭,把正在执行以及队列中等待的任务执行完毕再关闭,然后拒绝新的任务;

使用演示:

public class StopThreadPool {public static void main(String[] args) throws InterruptedException {ExecutorService executorService = Executors.newFixedThreadPool(10);for (int i = 0; i < 50; i++) {executorService.execute(new Runnable() {@Overridepublic void run() {try {Thread.sleep(500);System.out.println("执行已经提交的线程:" +Thread.currentThread().getName());} catch (InterruptedException e) {e.printStackTrace();}}});}Thread.sleep(1000);executorService.shutdown();executorService.execute(new Runnable() {@Overridepublic void run() {System.out.println("继续提交线程池");}});}
}

打印结果:

可以看出,shutdown后,再次提交是被拒绝的,抛出异常:RejectedExecutionException,但是已经提交的任务会慢慢执行完毕。

(2) isShutdown、isTerminated、awaitTermnation、shutdownNow

  • isShutdown:判断是否被shutdown了;
  • isTerminated:判断线程池是否完全终止;
  • awaitTermnation:检测一段时间后线程池是否完全终止,是用来检测的;
  • shutdownNow:立刻停止线程池,立刻停止的过程:利用线程InterruptedException中断抛出异常停止线程,同时该方法会返回一个集合,包含已经放进队列中还没来得急执行的线程对象;
//演示关闭线程池
public class ShutdownPool {public static void main(String[] args) throws InterruptedException {ExecutorService pool = Executors.newFixedThreadPool(10);for (int i = 0; i < 100; i++) {pool.execute(new ShutdownTask());}Thread.sleep(1500);pool.shutdownNow();boolean flag = pool.awaitTermination(3, TimeUnit.SECONDS);System.out.println(flag);System.out.println(pool.isShutdown());Thread.sleep(10000);System.out.println(pool.isTerminated());//再次执行任务pool.execute(new ShutdownTask());}
}class ShutdownTask implements Runnable{@Overridepublic void run() {try {Thread.sleep(500);System.out.println(Thread.currentThread().getName());} catch (InterruptedException e) {System.out.println(Thread.currentThread().getName()+": 被中断了");}}
}

打印结果:

三、常见线程池的特点和用法

线程池创建方法corePoolSizemaxPoolSizekeepAliveTimeworkQueue
newFixedThreadPool指定等于maxPoolSize0sLinkedBlockingQueue
newSingleThreadExecutor110sLinkedBlockingQueue
newCachedThreadPool0Integer.MAX_VALUE60sSynchronousQueue
newScheduledThreadPool指定Integer.MAX_VALUE0sDelayedWorkQueue

四、任务太多,怎么拒绝?

1、拒绝时机

出现以下其中一种情况时,会拒绝:

  • 一旦线程池shutDown,后续的任务就被拒绝了;
  • 当Executor对最大线程maxPoolSize 和工作队列容量使用ArrayBlockingQueue有限边界并饱和时候,就直接拒绝了;

2、拒绝策略

  • AbortPolicy,抛出异常;
  • DiscardPolicy,默默抛弃,不抛出异常;
  • DiscardOldestPolicy,丢弃最老的,存在时间最久的任务给丢掉;
  • CallerRunsPolicy,如果线程池没办法处理,谁提交的任务谁去跑,比如主线程提交的任务,那么线程池饱和的情况下,就交还给主线程执行这个任务;

3、钩子方法

每个任务执行前后,可以放置钩子方法,来做一些事情,比如日志、统计等;

//演示任务前后,可有钩子桉树
public class PauseableThreadPool extends ThreadPoolExecutor {private boolean isPaused;//标记位private final ReentrantLock lock = new ReentrantLock();//锁private final Condition unpaused = lock.newCondition();//类似wait/notify...//主函数public static void main(String[] args) throws InterruptedException {PauseableThreadPool pool = new PauseableThreadPool(10, 20, 10L, TimeUnit.SECONDS, new LinkedBlockingDeque<>());Runnable task = new Runnable() {@Overridepublic void run() {System.out.println("我被执行");try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}}};for (int i = 0; i < 10000; i++) {pool.execute(task);}Thread.sleep(1500);pool.pause();System.out.println("线程池被暂停了。。。。。。。。。。");Thread.sleep(1500);pool.resume();System.out.println("线程池恢复了。。。。。。。。。。。。");}//钩子函数,任务执行前@Overrideprotected void beforeExecute(Thread t, Runnable r) {super.beforeExecute(t, r);lock.lock();try {while (isPaused) {//暂停线程unpaused.await();}} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}//暂停private void pause() {lock.lock();try {isPaused = true;} finally {lock.unlock();}}//恢复private void resume() {lock.lock();try {isPaused = false;//唤醒线程unpaused.signalAll();} finally {lock.unlock();}}//下面↓↓↓ 是继承 ThreadPoolExecutor 默认实现的,不用管public PauseableThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);}public PauseableThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);}public PauseableThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);}public PauseableThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);}
}

打印结果:

五、线程池实现原理

1、线程池组成部分

  • 线程池管理器
  • 工作线程
  • 任务队列
  • 任务接口(Task)

2、Executor家族

  • Executor:接口,ExecutorService继承于Executor ;

  • Executors:工具类,帮我们自动创建线程池,类似于Collections;

  • ThreadPoolExecutor:线程池类,ThreadPoolExecutor继承于ExecutorService;

所以,Executor、ExecutorService、ThreadPoolExecutor都是线程池类型,只不过是继承关系;

3、线程池实现任务复用

相同线程执行不同任务,调用新的任务的run方法,在while中不停的循环执行

   // 源码final void runWorker(Worker w) {Runnable task = w.firstTask;while (task != null || (task = getTask()) != null) {task.run();}}

在runWork中,拿到一个又一个的task,while循环中判断如果不为空,则执行完毕task的run方法,如果task为空,即执行完毕了一个任务,则用getTask方法去取下一个任务,直到没有任务了。

4. execute()方法的源码

线程池的运行原理图:

结合上面的线程池运行原理图和下面的execute() 方法源码,梳理一下线程池的执行过程:

public void execute(Runnable command) {//判断提交的任务是否为 null, 是则抛出异常,commond:指接下来要执行的任务if (command == null)throw new NullPointerException();/** 获取线程池控制状态* ctl 是一个 AtomicInteger 变量 (骚操作)* jdk 8 中通过一个 int 值的前 28 位表示工作线程数量 workerCount, 剩余高位来表示 线程池状态* 计算 workerCount 和 runState 时通过掩码计算。  CAPACITY = (1 << 29) - 1* private static int runStateOf(int c)     { return c & ~CAPACITY; }* private static int workerCountOf(int c)  { return c & CAPACITY; }* */int c = ctl.get();//1. 当线程数小于 核心线程池容量时 将添加工作线程去执行任务if (workerCountOf(c) < corePoolSize) {if (addWorker(command, true)) // 这里的 core 为 true,表示当前线程数小于核心线程数,添加的线程在 核心线程数范围内,为false就是超过核心线程数,但是在最大线程数范围内return; c = ctl.get(); // 不成功则再次获取线程池控制状态}//2. (worker线程数量大于核心线程池容量时)如果线程池处于 RUNNING 状态,将命令加入 workQueue 队列if (isRunning(c) && workQueue.offer(command)) {int recheck = ctl.get(); //再次检查防止状态突变if (! isRunning(recheck) && remove(command)) //2.1 如果状态改变,线程池没有 RUNNING 则将命令移出队列,并拒绝执行reject(command); else if (workerCountOf(recheck) == 0) //2.2 状态没有改变,线程池 RUNNING,但 worker线程数量为 0, 则添加非core的worker线程addWorker(null, false);}//3. 如果线程池没有 RUNNING 并尝试添加非core的 worker 线程失败,那就拒绝执行else if (!addWorker(command, false)) reject(command);
}
  • 如果运行的线程小于 corePoolSize,则尝试使用用户定义的 Runnalbe 对象创建一个新的线程。调用 addWorker() 函数会原子性的检查runState 和 workCount,通过返回 false 来防止在不应该添加线程时添加了线程
  • 如果一个任务能够成功入队列,在添加一个线程时仍需要进行双重检查(可能因为在前一次检查后该线程死亡了),或者当进入到此方法时,线程池已经shutdown了,所以需要再次检查状态。若线程此时的状态不是 RUNNING,则需要回滚入队列操作;或者当线程池没有工作线程时,需要创建一个新的工作线程。
  • 如果无法入队列,那么需要增加一个新工作线程,如果此操作失败,那么就意味着线程池已经 SHUTDOWN 或者已经饱和了,所以拒绝任务

addWorker()方法的源码

此方法用来创建新的线程添加到线程池

private boolean addWorker(Runnable firstTask, boolean core) {retry:for (;;) {int c = ctl.get();int rs = runStateOf(c);/** 检查线程池状况, 确保此时可以添加新的线程,* 如果是runing,那么跳过if。* 如果rs>=SHUTDOWN,同时不等于SHUTDOWN,即为SHUTDOWN以上的状态,那么不接受新线程。* 如果rs>=SHUTDOWN,同时等于SHUTDOWN,同时first != null,那么拒绝新线程,* 如果为Empty,那么队列已空,不需要增加消耗线程,返回 false。* */if (rs >= SHUTDOWN &&! (rs == SHUTDOWN &&firstTask == null &&! workQueue.isEmpty()))return false;for (;;) {int wc = workerCountOf(c);/* * 判断线程池是否已满,如果线程数大于等于最大容量 CAPACITY 直接返回false* core 是一个boolean 参数,表明调用者想把此线程添加到哪个线程池* 根据 core 的值判断要添加的线程池是否已满**/if (wc >= CAPACITY ||wc >= (core ? corePoolSize : maximumPoolSize))return false;//CAS 操作增加工作线程数if (compareAndIncrementWorkerCount(c))break retry;c = ctl.get();  // Re-read ctl//CAS 操作失败, 再次检查状态重来一次if (runStateOf(c) != rs)continue retry;// else CAS failed due to workerCount change; retry inner loop}}boolean workerStarted = false;boolean workerAdded = false;Worker w = null;try {//创建新的线程w = new Worker(firstTask);final Thread t = w.thread;if (t != null) {final ReentrantLock mainLock = this.mainLock;//获取到锁mainLock.lock();try {// 再次检查状态,因为状态可能在获取锁之前改变int rs = runStateOf(ctl.get());//确保当前线程池还接收新的线程//结合上面的线程状态知道:当状态值大于等于 SHUTDOWN 时 线程池就不再接收新的线程了if (rs < SHUTDOWN ||(rs == SHUTDOWN && firstTask == null)) {if (t.isAlive()) // precheck that t is startablethrow new IllegalThreadStateException();workers.add(w);int s = workers.size();if (s > largestPoolSize)largestPoolSize = s;workerAdded = true;}} finally {mainLock.unlock();}if (workerAdded) {//线程添加成功后就可以启动线程准备执行了t.start();workerStarted = true;}}} finally {if (! workerStarted)addWorkerFailed(w);}return workerStarted;
}

从上面两段源码可以看到, 在添加新的线程进入线程池时,各种操作非常的严谨细致,往往需要多次检查状态,确保线程池的正确运行。

worker工作线程的源码

线程池创建线程时,会将线程封装成工作线程Worker,Worker在执行完任务后,还会无限循环获取工作队列里的任务来执行。我们可以从Worker的runWorker方法里看到:

final void runWorker(Worker w) {// 获取当前线程Thread wt = Thread.currentThread();// 获取w的firstTaskRunnable task = w.firstTask;// 设置w的firstTask为nullw.firstTask = null;// 释放锁(设置state为0,允许中断)w.unlock(); // allow interruptsboolean completedAbruptly = true;try {while (task != null || (task = getTask()) != null) { // 任务不为null或者阻塞队列还存在任务// 获取锁w.lock();/** 这里的检查主要是确保线程池此时还能接收新的任务去执行, 如果不在接收新的任务* 则应该中断当前线程**/if ((runStateAtLeast(ctl.get(), STOP) ||  (Thread.interrupted() &&  runStateAtLeast(ctl.get(), STOP))) &&   !wt.isInterrupted())    wt.interrupt(); try {// 在执行之前调用钩子函数beforeExecute(wt, task);Throwable thrown = null;try {// 运行给定的任务task.run();} catch (RuntimeException x) {thrown = x; throw x;} catch (Error x) {thrown = x; throw x;} catch (Throwable x) {thrown = x; throw new Error(x);} finally {// 执行完后调用钩子函数afterExecute(task, thrown);}} finally {task = null;// 增加给worker完成的任务数量w.completedTasks++;// 释放锁w.unlock();}}completedAbruptly = false;} finally {// 处理完成后,调用钩子函数processWorkerExit(w, completedAbruptly);}
}

5. 线程池的状态

  • RUNNING:接收新任务并处理排队任务,即执行execute后的状态;
  • SHUTDOWN:不接受新任务,但是处理排队任务,即执行shutdown方法后的状态;
  • STOP:不接受新任务,也不处理排队的任务,并中断正在进行的任务,即执行shutdownNow方法后的状态;
  • TIDYING:整洁,所有任务都已经终止,线程会转到TIDYING状态,并将运行 terminated() 方法;
  • TERMINATED:终止状态, terminated()方法完成;

状态转换图:

六、使用线程池的注意点

  • 避免任务堆积
  • 避免线程数过度增加
  • 排查线程泄漏:指线程执行完任务不能被回收,一般都是任务的逻辑出现问题,任务无法真正结束,现成也就无法被回收

点击关注我的公众号:点我关注

文章来源:java线程池

个人微信:CaiBaoDeCai

微信公众号名称:Java知者

微信公众号 ID: JavaZhiZhe

谢谢关注!


http://www.ppmy.cn/news/242922.html

相关文章

关于API接口的介绍及简单的API接口代码示例

API接口是指应用程序接口&#xff0c;是不同应用之间进行通讯的一种方法。它提供了一种简单的方式来交换数据和交互。通过API接口&#xff0c;不同的应用和服务可以进行互操作&#xff0c;为开发人员提供更多的开发可能性。 API接口具有许多优点。它们是通用的&#xff0c;这意…

r7 5700g对比5800x选哪个

锐龙7 5800X&#xff0c;单CCD&#xff0c;8核心16线程&#xff0c;4MB二级缓存&#xff0c;32MB三级缓存&#xff0c;基准频率3.8GHz&#xff0c;最高加速4.7GHz&#xff0c;热设计功耗为105W。 组装电脑选5800x还是R5 5700g怎么搭配更合适这些点很重要 http://www.adiannao.c…

达人评测 r7 3700x和r5 5600g选哪个好

R5 5600G配备了6核12线程&#xff0c;拥有3MB二级缓存和三级缓存16MB&#xff0c;基础频率为3.9GHz&#xff0c;最高加速 为4.4GHz&#xff0c;内置核显为Vega 7 GPU&#xff0c;核显频率为1900MHz 组装电脑选r5 5600g还是r7 3700x怎么搭配更合适这些点很重要 http://www.adian…

r7 5800h和i5 12500h哪个好

R7 5800H 为 8 核 16 线程&#xff0c;主频 3.2GHz&#xff0c;三级缓存翻倍至 16MB&#xff0c;搭载了 DDR4-3200 内存。 选R7 5800H还是i5 12500h这些点很重要!看完你就知道了 http://www.adiannao.cn/dy i5-12500h参数配置&#xff1a;其工艺也是10nm&#xff0c;2.5GHz的主…

华硕 ROG PG27UQR 电竞显示器参数 华硕 ROG PG27UQR 评测

华硕 ROG PG27UQR 电竞显示器&#xff08;27’/4K / 160Hz / HDR600 / Fast IPS / 1ms&#xff09;&#xff0c;定价 5699 元&#xff0c;首发到手价 4699 元。 华硕 ROG PG27UQR更多使用感受和评价 http://rog.jd.com 这款显示器采用了 27 英寸 Fast IPS 面板&#xff0c;拥…

Day12 性能测试详解——什么是性能测试?、性能测试分类及常用指标、性能测试流程、总结

Day12 性能测试详解——什么是性能测试?、性能测试分类及常用指标、性能测试流程、总结 文章目录 Day12 性能测试详解——什么是性能测试?、性能测试分类及常用指标、性能测试流程、总结什么是性能测试?一、为什么要学习性能测试1.1 业务需求 解决方案1.2 问题二、什么是性能…

vue3+uniapp开发小程序踩坑指南(持续更新)

小程序常见问题汇总&#xff1a; 1、TypeError: Cannot read property forceUpdate of undefined 原因&#xff1a;没有配置小程序AppID 2、define is not defined 报错一堆文件找不到&#xff0c;并且有define is not defined错误提示 原因&#xff1a;没有配置基础库或者基…

一文带你进入自动化测试

8年前我刚进入到IT行业&#xff0c;现在发现学习软件测试的人越来越多&#xff0c;今天我想根据自己的行业经验给大家提一些建议。 最近聊到软件测试的行业内卷&#xff0c;越来越多的转行和大学生进入测试行业。想要获得更好的待遇和机会&#xff0c;不断提升自己的技能栈成了…