Java线程池分析

1. Executor框架

Executor是一套线程池管理框架,接口里只有一个方法execute,执行Runnable任务。ExecutorService接口扩展了Executor,添加了线程生命周期的管理,提供任务终止、返回任务结果等方法。AbstractExecutorService实现了ExecutorService,提供例如submit方法的默认实现逻辑。
ThreadPoolExecutor,继承了AbstractExecutorService,提供线程池的具体实现。
ThreadPoolExecutor的构造函数
public ThreadPoolExecutor(int corePoolSize, 
                                               int maximumPoolSize, 
                                               long keepAliveTime, 
                                               TimeUnit unit, 
                                               BlockingQueue<Runnable> workQueue, 
                                               ThreadFactory threadFactory,                                                 
                                               RejectedExecutionHandler handler) { }

等待队列

ThreadPoolExecutor提供一个阻塞队列来保存因线程不足而等待的Runnable任务,这就是BlockingQueue。

JDK为BlockingQueue提供了几种实现方式,常用的有:

  • ArrayBlockingQueue:数组结构的阻塞队列
  • LinkedBlockingQueue:链表结构的阻塞队列
  • PriorityBlockingQueue:有优先级的阻塞队列
  • SynchronousQueue:不会存储元素的阻塞队列
SynchronousQueue十分有趣,看名称是个队列,但它却不能存储元素。要将一个任务放进队列,必须有另一个线程去接收这个任务,一个进就有一个出,队列不会存储任何东西。因此,SynchronousQueue是一种移交机制,不能算是队列。(直接提交给线程池

饱和策略

当有界的等待队列满了之后,就需要用到饱和策略去处理,ThreadPoolExecutor的饱和策略通过传入RejectedExecutionHandler来实现。

  • AbortPolicy是默认的实现,直接抛出一个RejectedExecutionException异常,让调用者自己处理。
public static class AbortPolicy implements RejectedExecutionHandler { public AbortPolicy() { } public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString()); } }
  • DiscardPolicy的rejectedExecution直接是空方法,什么也不干。如果队列满了,后续的任务都抛弃掉。
public static class DiscardPolicy implements RejectedExecutionHandler { public DiscardPolicy() { } public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { } }
  • DiscardOldestPolicy会将等待队列里最旧的任务踢走,让新任务得以执行。
public static class DiscardOldestPolicy implements RejectedExecutionHandler { public DiscardOldestPolicy() { } public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { e.getQueue().poll(); e.execute(r); } } }
  • 最后一种饱和策略是CallerRunsPolicy,它既不抛弃新任务,也不抛弃旧任务,而是直接在当前线程运行这个任务。(一般是主线程,可能导致主线程阻塞)
public static class CallerRunsPolicy implements RejectedExecutionHandler { public CallerRunsPolicy() { } public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { r.run(); } } }

ThreadFactory

每当线程池需要创建一个新线程,都是通过线程工厂获取。如果不为ThreadPoolExecutor设定一个线程工厂,就会使用默认的defaultThreadFactory。


2. 线程池的执行

线程池状态

首先认识两个贯穿线程池代码的参数:

  • runState:线程池运行状态
  • workerCount:工作线程的数量

线程池用一个32位的int来同时保存runState和workerCount,其中高3位是runState,其余29位是workerCount。代码中会反复使用runStateOf和workerCountOf来获取runState和workerCount。

  • RUNNING:可接收新任务,可执行等待队列里的任务
  • SHUTDOWN:不可接收新任务,可执行等待队列里的任务
  • STOP:不可接收新任务,不可执行等待队列里的任务,并且尝试终止所有在运行任务
  • TIDYING:所有任务已经终止,执行terminated()
  • TERMINATED:terminated()执行完成

Worker的创建

线程池是由Worker类负责执行任务,Worker继承了AbstractQueuedSynchronizer,引出了Java并发框架的核心AQS。

  • AbstractQueuedSynchronizer,简称AQS,是Java并发包里一系列同步工具的基础实现,原理是根据状态位来控制线程的入队阻塞、出队唤醒来处理同步。

线程池创建线程的四种情况
  • 如果目前请求工作的线程小于核心线程数,则通过addWorker增加线程
  • 请求工作的线程数大于核心线程数,小于缓存队列size,任务加入到缓存队列中
  • 缓存队列已满,最大线程数还有空间,通过addWorker增加线程
  • 最大线程数也填满,新提交的任务交给handler处理。


添加线程的过程
主要做了两件事,第一是工作线程数加一,使用CAS机制保证workerCount正确地递增。
第二件事是创建一个新的Worker对象,将Worker对象添加到workers集合(Set集合)中,然后启动worker中的线程


Worker是一个很重要的类,线程池创建线程都是通过Worker实现的。
同时可以看到, Worker继承了AbstractQueuedSynchronizer,也就是著名的AQS。
Worker在构造函数里采用ThreadFactory创建Thread,在run方法里调用了runWorker,runWorker是真正执行任务的地方。

在runWorker方法中,第一步从getTask获取需要执行的任务,达到线程复用的效果;
第二步复杂的判断保证了线程池在STOP状态下线程是中断的,非STOP状态下线程没有被中断;
第三步真正执行了task的任务;
第四步 completedTasks统计worker执行了多少任务,completedAbruptly表示worker是否异常终止,执行到这里代表执行正常;
第五步调用processWorkerExit结束。

再来看看getTask方法,下面是重点
getTask主要作用是从workQueue队列中获取任务,然后线程池中的线程去执行任务,如果没有就阻塞。

线程池的关闭

线程池的关闭不是一关了事,worker在池里处于不同状态,必须安排好worker的"后事",才能真正释放线程池。ThreadPoolExecutor提供两种方法关闭线程池:

  • shutdown:不能再提交任务,已经提交的任务可继续运行;
  • shutdownNow:不能再提交任务,已经提交但未执行的任务不能运行,在运行的任务可继续运行,但会被中断,返回已经提交但未执行的任务。
shutdown将线程池切换到SHUTDOWN状态,并调用interruptIdleWorkers请求中断所有空闲的worker,最后调用tryTerminate尝试结束线程池。

shutdownNow和shutdown类似,将线程池切换为STOP状态,中断目标是所有worker。drainQueue会将等待队列里未执行的任务返回。

interruptIdleWorkers和interruptWorkers实现原理都是遍历workers集合,中断条件符合的worker。


3. 线程池复用线程的原理
线程池的模型是“生产者-消费者”的模型,使用阻塞队列BlockingQueue实现。这是线程池的本质。
当没有达到核心线程数时,线程池会创建线程去处理任务。同时,这些线程并不会停止,他们共同组成了消费者。如果没有任务,这些线程会阻塞等待任务的到来。
当“生产者”继续提交任务时,线程池将这些任务放在阻塞队列BlockingQueue中,线程池中的线程如果空闲,会去消费BlockingQueue中的任务,所以,线程池可以通过少数几个线程来处理大量的任务,这就是线程池复用线程的原理。


猜你喜欢

转载自blog.csdn.net/ningdunquan/article/details/79910341