源码| 线程池的实现原理

线程池的实现原理

一.概述

线程池,顾名思义就是存放线程的池子,池子里存放了很多可以复用的线程。

如果不用类似线程池的容器,每当我们需要执行用户任务的时候都去创建新的线程,任务执行完之后线程就被回收了,这样频繁地创建和销毁线程会浪费大量的系统资源。

因此,线程池通过线程复用机制,并对线程进行统一管理,具有以下优点:

  • 降低系统资源消耗。通过复用已存在的线程,降低线程创建和销毁造成的消耗;

  • 提高响应速度。当有任务到达时,无需等待新线程的创建便能立即执行;

  • 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗大量系统资源,还会降低系统的稳定性,使用线程池可以进行对线程进行统一的分配、调优和监控。

ThreadPoolExecutor是线程池框架的一个核心类,本文通过对ThreadPoolExecutor源码的分析(基于JDK 1.8),来深入分析线程池的实现原理。

二.ThreadPoolExecutor类的属性

先从ThreadPoolExecutor类中的属性字段开始:

// 线程池的控制状态,用高3位来表示线程池的运行状态,低29位来表示线程池中工作线程的数量  
private  final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));  
//值为29,用来表示偏移量  
private  static  final  int COUNT_BITS = Integer.SIZE - 3;  
//线程池的最大容量,其值的二进制为:00011111111111111111111111111111(29个1)  
private  static  final  int CAPACITY = (1 << COUNT_BITS) - 1;  
  
// 线程池的运行状态,总共有5个状态,用高3位来表示  
private  static  final  int RUNNING = -1 << COUNT_BITS;  
private  static  final  int SHUTDOWN = 0 << COUNT_BITS;  
private  static  final  int STOP = 1 << COUNT_BITS;  
private  static  final  int TIDYING = 2 << COUNT_BITS;  
private  static  final  int TERMINATED = 3 << COUNT_BITS;  
  
//任务缓存队列,用来存放等待执行的任务  
private  final BlockingQueue<Runnable> workQueue;  
  
//全局锁,对线程池状态等属性修改时需要使用这个锁  
private  final ReentrantLock mainLock = new ReentrantLock();  
  
//线程池中工作线程的集合,访问和修改需要持有全局锁  
private  final HashSet<Worker> workers = new HashSet<Worker>();  
  
// 终止条件  
private  final Condition termination = mainLock.newCondition();  
  
//线程池中曾经出现过的最大线程数  
private  int largestPoolSize;  
  
//已完成任务的数量  
private  long completedTaskCount;  
  
//线程工厂  
private  volatile ThreadFactory threadFactory;  
  
//任务拒绝策略  
private  volatile RejectedExecutionHandler handler;  
  
//线程存活时间  
private  volatile  long keepAliveTime;  
  
//是否允许核心线程超时  
private  volatile  boolean allowCoreThreadTimeOut;  
  
//核心池大小,若allowCoreThreadTimeOut被设置,核心线程全部空闲超时被回收的情况下会为0  
private  volatile  int corePoolSize;  
  
//最大池大小,不得超过CAPACITY  
private  volatile  int maximumPoolSize;  
  
//默认的任务拒绝策略  
private  static  final RejectedExecutionHandler defaultHandler =  
new AbortPolicy();  
  
private  static  final RuntimePermission shutdownPerm =  
new RuntimePermission("modifyThread");  
  
private  final AccessControlContext acc;

在ThreadPoolExecutor类的这些属性中,线程池状态是控制线程池生命周期至关重要的属性,这里就以线程池状态为出发点进行研究。
在这里插入图片描述

通过上面的源码可知,线程池的运行状态总共有5种,其值和含义分别如下:

  • RUNNING: 高3位为111,接受新任务并处理阻塞队列中的任务;
  • SHUTDOWN: 高3位为000,不接受新任务但会处理阻塞队列中的任务;线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务。
  • STOP:高3位为001,不会接受新任务,也不会处理阻塞队列中的任务,并且中断正在运行的任务;
  • TIDYING: 高3位为010,所有任务都已终止,工作线程数量为0,线程池将转化到TIDYING状态,即将要执行terminated()钩子方法;当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。
  • TERMINATED: 高3位为011,terminated()方法已经执行结束;线程池彻底终止,就变成TERMINATED状态。

然而,线程池中并没有使用单独的变量来表示线程池的运行状态,而是使用一个AtomicInteger类型的变量ctl来表示线程池的控制状态,其将线程池运行状态与工作线程的数量打包在一个整型中,用高3位来表示线程池的运行状态,低29位来表示线程池中工作线程的数量,对ctl的操作主要参考以下几个函数:

// 通过与的方式,获取ctl的高3位,也就是线程池的运行状态  
private static int runStateOf(int c) { return c & ~CAPACITY; }  
//通过与的方式,获取ctl的低29位,也就是线程池中工作线程的数量  
private static int workerCountOf(int c) { return c & CAPACITY; }  
//通过或的方式,将线程池状态和线程池中工作线程的数量打包成ctl  
private static int ctlOf(int rs, int wc) { return rs | wc; }  
//SHUTDOWN状态的值是0,比它大的均是线程池停止或清理状态,比它小的是运行状态  
private static boolean isRunning(int c) {  
	return c < SHUTDOWN;  
}

接下来,我们看一下线程池状态的所有转换情况,如下:

  • RUNNING -> SHUTDOWN:调用shutdown(),可能在finalize()中隐式调用;
  • (RUNNING or SHUTDOWN) -> STOP:调用shutdownNow();
  • SHUTDOWN -> TIDYING:当缓存队列和线程池都为空时;
  • STOP -> TIDYING:当线程池为空时;
  • TIDYING -> TERMINATED:当terminated()方法执行结束时;

通常情况下,线程池有如下两种状态转换流程:

  1. RUNNING -> SHUTDOWN -> TIDYING -> TERMINATED
  2. RUNNING -> STOP -> TIDYING -> TERMINATED

三.ThreadPoolExecutor类的构造方法

通常情况下,我们使用线程池的方式就是new一个ThreadPoolExecutor对象来生成一个线程池。接下来,先看ThreadPoolExecutor类的构造函数:

//间接调用最后一个构造函数,采用默认的任务拒绝策略AbortPolicy和默认的线程工厂  
public ThreadPoolExecutor(int corePoolSize,  
							int maximumPoolSize,  
							long keepAliveTime,  
							TimeUnit unit,  
							BlockingQueue<Runnable> workQueue);  

//间接调用最后一个构造函数,采用默认的任务拒绝策略AbortPolicy  
public ThreadPoolExecutor(int corePoolSize,  
							int maximumPoolSize,  
							long keepAliveTime,  
							TimeUnit unit,  
							BlockingQueue<Runnable> workQueue,  
							ThreadFactory threadFactory);  

//间接调用最后一个构造函数,采用默认的线程工厂  
public ThreadPoolExecutor(int corePoolSize,  
							int maximumPoolSize,  
							long keepAliveTime,  
							TimeUnit unit,  
							BlockingQueue<Runnable> workQueue,  
							RejectedExecutionHandler handler);  

//前面三个分别调用了最后一个,主要的构造函数  
public ThreadPoolExecutor(int corePoolSize,  
							int maximumPoolSize,  
							long keepAliveTime,  
							TimeUnit unit,  
							BlockingQueue<Runnable> workQueue,  
							ThreadFactory threadFactory,  
							RejectedExecutionHandler handler);

接下来,看下最后一个构造函数的具体实现:

public ThreadPoolExecutor(int corePoolSize,  
							int maximumPoolSize,  
							long keepAliveTime,  
							TimeUnit unit,  
							BlockingQueue<Runnable> workQueue,  
							ThreadFactory threadFactory,  
							RejectedExecutionHandler handler) 
{  
	//参数合法性校验  
	if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0)  
		throw  new IllegalArgumentException();  
	//参数合法性校验  
	if (workQueue == null || threadFactory == null || handler == null)  
		throw  new NullPointerException();  
	this.acc = System.getSecurityManager() == null ? null : AccessController.getContext();
	//初始化对应的属性
	this.corePoolSize = corePoolSize;  
	this.maximumPoolSize = maximumPoolSize;  
	this.workQueue = workQueue;  
	this.keepAliveTime = unit.toNanos(keepAliveTime);  
	this.threadFactory = threadFactory;  
	this.handler = handler;  
}

下面解释下一下构造器中各个参数的含义:

1.corePoolSize
线程池中的核心线程数。当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize;如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行。
2.maximumPoolSize
线程池中允许的最大线程数。如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSize。

3.keepAliveTime
线程空闲时的存活时间。默认情况下,==只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。==但是如果调用了allowCoreThreadTimeOut(boolean)方法,keepAliveTime参数也会起作用,直到线程池中的线程数为0。

4.unit
keepAliveTime参数的时间单位。

5.workQueue
任务缓存队列,用来存放等待执行的任务。如果当前线程数为corePoolSize,继续提交的任务就会被保存到任务缓存队列中,等待被执行。

一般来说,这里的BlockingQueue有以下三种选择:

  • SynchronousQueue:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态。因此,如果线程池中始终没有空闲线程(任务提交的平均速度快于被处理的速度),可能出现无限制的线程增长。

  • LinkedBlockingQueue:基于链表结构的阻塞队列,如果不设置初始化容量,其容量为Integer.MAX_VALUE,即为无界队列。因此,如果线程池中线程数达到了corePoolSize,且始终没有空闲线程(任务提交的平均速度快于被处理的速度),任务缓存队列可能出现无限制的增长。

  • ArrayBlockingQueue:基于数组结构的有界阻塞队列,按FIFO排序任务。

6.threadFactory
线程工厂,创建新线程时使用的线程工厂。

7.handler
任务拒绝策略,当阻塞队列满了,且线程池中的线程数达到maximumPoolSize,如果继续提交任务,就会采取任务拒绝策略处理该任务,线程池提供了4种任务拒绝策略:

  • AbortPolicy:丢弃任务并抛出RejectedExecutionException异常,默认策略;

  • DiscardPolicy:丢弃任务,但是不抛出异常;

  • DiscardOldestPolicy:丢弃阻塞队列最前面的任务,然后重新尝试执行任务(重复此过程)。

  • CallerRunsPolicy:由调用execute方法的线程执行该任务;

    当然也可以根据应用场景实现RejectedExecutionHandler接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务。

四.线程池的实现原理

1.提交任务

线程池框架提供了两种方式提交任务,submit()和execute(),通过submit()方法提交的任务可以返回任务执行的结果,通过execute()方法提交的任务不能获取任务执行的结果

submit()方法的实现有以下三种:

public Future<?> submit(Runnable task);  
public <T> Future<T> submit(Runnable task, T result);  
public <T> Future<T> submit(Callable<T> task);

下面以第一个方法为例简单看一下submit()方法的实现:

public Future<?> submit(Runnable task) {  
	if (task == null) throw  new NullPointerException();  
	RunnableFuture<Void> ftask = newTaskFor(task, null);  
	execute(ftask);  
	return ftask;  
}

submit()方法是在ThreadPoolExecutor的父类AbstractExecutorService类实现的,最终还是调用的ThreadPoolExecutor类的execute()方法,下面着重看一下execute()方法的实现。

public void execute(Runnable command) {  
	if (command == null)  
		throw  new NullPointerException();  
	//获取线程池控制状态  
	int c = ctl.get();  
	// (1)  
	//worker数量小于corePoolSize  
	if (workerCountOf(c) < corePoolSize) {  
		//创建worker,addWorker方法boolean参数用来判断是否创建核心线程  
		if (addWorker(command, true))  
			//成功则返回  
			return;  
		//失败则再次获取线程池控制状态  
		c = ctl.get();  
	}  
	//(2)  
	//线程池处于RUNNING状态,将任务加入workQueue任务缓存队列  
	if (isRunning(c) && workQueue.offer(command)) {  
		// 再次检查,获取线程池控制状态,防止在任务入队的过程中线程池关闭了或者线程池中没有线程了  
		int recheck = ctl.get();  
		//线程池不处于RUNNING状态,且将任务从workQueue移除成功  
		if (! isRunning(recheck) && remove(command))  
			//采取任务拒绝策略  
			reject(command);  
		//worker数量等于0  
		else  if (workerCountOf(recheck) == 0)  
			//创建worker  
			addWorker(null, false);  
	}  
	//(3)  
	else  if (!addWorker(command, false)) //创建worker  
		reject(command); //如果创建worker失败,采取任务拒绝策略  
}

execute()方法的执行流程可以总结如下:

  • 若线程池工作线程数量小于corePoolSize,则创建新线程来执行任务;
  • 若工作线程数量大于或等于corePoolSize,则将任务加入BlockingQueue(加入到阻塞队列);
  • 若无法将任务加入BlockingQueue(BlockingQueue已满),且工作线程数量小于maximumPoolSize,则创建新的线程来执行任务;
  • 若工作线程数量达到maximumPoolSize,则创建线程失败,采取任务拒绝策略;

2.创建线程

从execute()方法的实现可以看出,addWorker()方法主要负责创建新的线程并执行任务,代码实现如下:


//addWorker有两个参数:Runnable类型的firstTask,用于指定新增的线程执行的第一个任务;boolean类型的core,表示是否创建核心线程
//该方法的返回值代表是否成功新增一个线程 
private boolean addWorker(Runnable firstTask, boolean core) {        
	retry:        
	for (;;) {            
		int c = ctl.get();            
		int rs = runStateOf(c);            
		// (1)            
		if (rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask == null &&                   ! workQueue.isEmpty()))                
			return false;            
		for (;;) {                
			int wc = workerCountOf(c);                //线程数超标,不能再创建线程,直接返回                
			if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize))                    
				return false;                
			//CAS操作递增workCount                
			//如果成功,那么创建线程前的所有条件校验都满足了,准备创建线程执行任务,退出retry循环                
			//如果失败,说明有其他线程也在尝试往线程池中创建线程(往线程池提交任务可以是并发的),则继续往下执行                
			if (compareAndIncrementWorkerCount(c))                    
				break retry;                
			//重新获取线程池控制状态                
			c = ctl.get();                
			// 如果线程池的状态发生了变更,如有其他线程关闭了这个线程池,那么需要回到外层的for循环                
			if (runStateOf(c) != rs)                    
				continue retry;                
			//如果只是CAS操作失败的话,进入内层的for循环就可以了            
		}        
	}//到这里,创建线程前的所有条件校验都满足了,可以开始创建线程来执行任务    
	//worker是否已经启动        
	boolean workerStarted = false;        
	//是否已将这个worker添加到workers这个HashSet中        
	boolean workerAdded = false;        
	Worker w = null;        
	try {            //创建一个worker,从这里可以看出对线程的包装            
		w = new Worker(firstTask);            
		//取出worker中的线程对象,Worker的构造方法会调用ThreadFactory来创建一个新的线程            
		final Thread t = w.thread;            
		if (t != null) {                
		//获取全局锁, 并发的访问线程池workers对象必须加锁,持有锁的期间线程池也不会被关闭
		   final ReentrantLock mainLock = this.mainLock;   
		   mainLock.lock();                
		   try {                    
			   //重新获取线程池的运行状态                    
			   int rs = runStateOf(ctl.get());                    
			   //小于SHUTTDOWN即RUNNING                    
			   //等于SHUTDOWN并且firstTask为null,不接受新的任务,但是会继续执行等待队列中的任务                    
			   if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) {                        
				   //worker里面的thread不能是已启动的                       
				   if (t.isAlive())                            
					   throw new IllegalThreadStateException();                       
				   //将新创建的线程加入到线程池中                        
				   workers.add(w);                        
				   int s = workers.size();                        
				   // 更新largestPoolSize                        
				   if (s > largestPoolSize)                            	
					   largestPoolSize = s;                        
				   workerAdded = true;                    
			   }                
		   } finally {                    
			   mainLock.unlock();                
		   }               
		   //线程添加线程池成功,则启动新创建的线程                
		   if (workerAdded) {                    
			   t.start();                    
			   workerStarted = true;                
		   }            
	   }        
   } finally {            
	   //若线程启动失败,做一些清理工作,例如从workers中移除新添加的worker并递减wokerCount            
	   if (! workerStarted)                
		   addWorkerFailed(w);        
   }        
   //返回线程是否启动成功        
   return workerStarted;    
}

因为代码(1)处的逻辑不利于理解,我们通过(1)的等价实现来理解:

if (rs>=SHUTDOWN && !(rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty()))
//等价实现rs>=SHUTDOWN && (rs != SHUTDOWN || firstTask != null || workQueue.isEmpty())

其含义为,满足下列条件之一则直接返回false,线程创建失败:

  • rs > SHUTDOWN,也就是STOP,TIDYING或TERMINATED,此时不再接受新的任务,且中断正在执行的任务

  • rs = SHUTDOWN且firstTask != null,此时不再接受任务,但是仍会处理任务缓存队列中的任务

  • rs = SHUTDOWN,队列为空

多说一句,若线程池处于 SHUTDOWN, firstTask 为 null,且 workQueue 非空,那么还得创建线程继续处理任务缓存队列中的任务。

总结一下,addWorker()方法完成了如下几件任务:

1.原子性的增加workerCount;
2.将用户给定的任务封装成为一个worker,并将此worker添加进workers集合中;
3.启动worker对应的线程;
4.若线程启动失败,回滚worker的创建动作,即从workers中移除新添加的worker,并原子性的减少workerCount;

3.工作线程的实现

从addWorker()方法的实现可以看出,工作线程的创建和启动都跟ThreadPoolExecutor中的内部类Worker有关(提交的任务都要封装成worker线程然后 添加到workers队列中然后再启动线程执行任务)。下面我们分析Worker类来看一下工作线程的实现。

Worker类继承自AQS类,具有锁的功能;实现了Runable接口,可以将自身作为一个任务在线程中执行( Worker类是一个线程类 )。

private final class Worker extends AbstractQueuedSynchronizer        
							implements Runnable

Worker的主要字段就下面三个,代码也比较简单。

//用来封装worker的线程,线程池中真正运行的线程,通过线程工厂创建而来  
final Thread thread;  
//worker所对应的第一个任务,可能为空  
Runnable firstTask;  
//记录当前线程完成的任务数  
volatile long completedTasks;`

Worker的构造函数如下。

Worker(Runnable firstTask) {  
	//设置AQS的state为-1,在执行runWorker()方法之前阻止线程中断  
	setState(-1);  
	//初始化第一个任务  
	this.firstTask = firstTask;  
	//利用指定的线程工厂创建一个线程,注意,参数是Worker实例本身this  
	//也就是当执行start方法启动线程thread时,真正执行的是Worker类的run方法  
	this.thread = getThreadFactory().newThread(this);  
}

Worker类继承了AQS类,重写了其相应的方法,实现了一个自定义的同步器,实现了不可重入锁。

`//是否持有独占锁  
protected boolean isHeldExclusively() {  
	return getState() != 0;  
}  
//尝试获取锁  
protected boolean tryAcquire(int unused) {  
	if (compareAndSetState(0, 1)) {  
		//设置独占线程  
		setExclusiveOwnerThread(Thread.currentThread());  
		return true;  
	}  
	return false;  
}  
//尝试释放锁  
protected boolean tryRelease(int unused) {  
	//设置独占线程为null  
	setExclusiveOwnerThread(null);  
	setState(0);  
	return true;  
}  
//获取锁  
public void lock() { acquire(1); }  
//尝试获取锁  
public boolean tryLock() { return tryAcquire(1); }  
//释放锁  
public void unlock() { release(1); }  
//是否持有锁  
public boolean isLocked() { return isHeldExclusively(); }

Worker类还提供了一个中断线程thread的方法。

void interruptIfStarted() {  
	Thread t;  
	//AQS状态大于等于0,worker对应的线程不为null,且该线程没有被中断  
	if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {  
		try {  
			t.interrupt();  
		} catch (SecurityException ignore) {  
			//...
		}  
	}  
}

再来看一下Worker类的run()方法的实现,会发现run()方法最终调用了ThreadPoolExecutor类的runWorker()方法。

public void run() {  
	runWorker(this);  
}

4.线程复用机制

通过上文可以知道,worker中的线程start 后,执行的是worker的run()方法,而run()方法最终会调用ThreadPoolExecutor类的runWorker()方法,runWorker()方法实现了线程池中的线程复用机制。下面我们来看一下runWorker()方法的实现。

final void runWorker(Worker w) {        
	//获取当前线程        
	Thread wt = Thread.currentThread();        
	//获取w的firstTask        
	Runnable task = w.firstTask;        
	//设置w的firstTask为null        
	w.firstTask = null;        
	// 释放锁,设置AQS的state为0,允许中断        
	w.unlock();        
	//用于标识线程是否异常终止,finally中processWorkerExit()方法会有不同逻辑        
	boolean completedAbruptly = true;        
	try {            
		//循环调用getTask()获取任务,不断从任务缓存队列获取任务并执行            
		while (task != null || (task = getTask()) != null) {                
			//进入循环内部,代表已经获取到可执行的任务,则对worker对象加锁,保证线程在执行任务过程中不会被中断                
			w.lock();                //若线程池状态大于等于STOP,那么意味着该线程要中断  
			if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) &&!wt.isInterrupted())           
				//且是因为线程池内部状态变化而被中断       
				//线程被中断  
				//确保该线程未被中断                    
				//发出中断请求                    
				wt.interrupt();                
			try {                    
				//开始执行任务前的Hook方法                    
				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 {                        
					//执行任务后的Hook方法                        
					afterExecute(task, thrown);                    
				}                
			} finally {                    
				//置空task,准备通过getTask()获取下一个任务                    
				task = null;                    
				//completedTasks递增                    
				w.completedTasks++;                    
				//释放掉worker持有的独占锁                    
				w.unlock();                
			}            
		}            
		ompletedAbruptly = false;        
	} finally {            
		//到这里,线程执行结束,需要执行结束线程的一些清理工作            
		//线程执行结束可能有两种情况:            
		//1.getTask()返回null,也就是说,这个worker的使命结束了,线程执行结束            
		//2.任务执行过程中发生了异常            
		//第一种情况,getTask()返回null,那么getTask()中会将workerCount递减            
		//第二种情况,workerCount没有进行处理,这个递减操作会在processWorkerExit()中处理            
		processWorkerExit(w, completedAbruptly);        
	}    
}

runWorker()方法是线程池的核心,实现了线程池中的线程复用机制,来看一下

runWorker()方法都做了哪些工作:

  1. 运行第一个任务firstTask之后,循环调用getTask()方法获取任务,不断从任务缓存队列获取任务并执行

  2. 获取到任务之后就对worker对象加锁,保证线程在执行任务的过程中不会被中断,任务执行完会释放锁;

  3. 在执行任务的前后,可以根据业务场景重写beforeExecute()和afterExecute()等Hook方法;

  4. 执行通过getTask()方法获取到的任务;

  5. 线程执行结束后,调用processWorkerExit()方法执行结束线程的一些清理工作;

从runWorker()方法的实现可以看出,runWorker()方法中主要调用了getTask()方法和processWorkerExit()方法,下面分别看一下这两个方法的实现。

getTask()方法用来不断地从任务缓存队列获取任务并交给线程执行,下面分析一下其实现。

private Runnable getTask() {        
	//标识当前线程是否超时未能获取到task对象        
	boolean timedOut = false;        
	for (;;) {            //获取线程池的控制状态            
		int c = ctl.get(); //获取线程池的运行状态            
		int rs = runStateOf(c);//如果线程池状态大于等于STOP,或者处于SHUTDOWN状态,并且阻塞队列为空,线程池工作线程数量递减,方法返回null,回收线程            
		if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) { 
			decrementWorkerCount();                
			return null;            
		}                        /
		/获取worker数量            
		int wc = workerCountOf(c);            
		//标识当前线程在空闲时,是否应该超时回收            
		// 如果allowCoreThreadTimeOut为ture,或当前线程数大于核心池大小,则需要超时回收            
		boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;                        
		//如果worker数量大于maximumPoolSize(有可能调用了 setMaximumPoolSize(),导致worker数量大于maximumPoolSize)           
		                                 //或者获取任务超时 
		if ((wc > maximumPoolSize || (timed && timedOut))&& (wc > 1 || workQueue.isEmpty())) {
			//workerCount大于1或者阻塞队列为空(在阻塞队列不为空时,需要保证至少有一个工作线程)                
			if (compareAndDecrementWorkerCount(c))//线程池工作线程数量递减,方法返回null,回收线程                    
				return null; //线程池工作线程数量递减失败,跳过剩余部分,继续循环                
			continue;            
		}            
		try {                
			//如果允许超时回收,则调用阻塞队列的poll(),只在keepAliveTime时间内等待获取任务,一旦超过则返回null                
			//否则调用take(),如果队列为空,线程进入阻塞状态,无限时等待任务,直到队列中有可取任务或者响应中断信号退出                
			Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :                    workQueue.take();                
			//若task不为null,则返回成功获取的task对象                
			if (r != null) return r; // 若返回task为null,表示线程空闲时间超时,则设置timeOut为true                
			timedOut = true;            
		} catch (InterruptedException retry) {                
			//如果此worker发生了中断,采取的方案是重试,没有超时                
			//在哪些情况下会发生中断?调用setMaximumPoolSize(),shutDown(),shutDownNow()                
			timedOut = false;            
		}        
	}    
}

接下来总结一下getTask()方法会在哪些情况下返回:

  1. 线程池处于RUNNING状态,阻塞队列不为空,返回成功获取的task对象;

  2. 线程池处于SHUTDOWN状态,阻塞队列不为空,返回成功获取的task对象;

  3. 线程池状态大于等于STOP,返回null,回收线程;

  4. 线程池处于SHUTDOWN状态,并且阻塞队列为空,返回null,回收线程;

  5. worker数量大于maximumPoolSize,返回null,回收线程;

  6. 线程空闲时间超时,返回null,回收线程;

processWorkerExit()方法负责执行结束线程的一些清理工作,下面分析一下其实现。
在这里插入图片描述

processWorkerExit()方法中主要调用了tryTerminate()方法,下面看一下tryTerminate()方法的实现。
在这里插入图片描述

tryTerminate()方法的作用是尝试终止线程池,它会在所有可能终止线程池的地方被调用。

满足终止线程池的条件有两个:

  1. 首先,线程池状态为STOP,或者为SHUTDOWN且任务缓存队列为空;
  2. 其次,工作线程数量为0。

满足了上述两个条件之后,tryTerminate()方法获取全局锁,设置线程池运行状态为TIDYING,之后执行terminated()钩子方法,最后设置线程池状态为TERMINATED。

至此,线程池运行状态变为TERMINATED,工作线程数量为0,workers已清空,且workQueue也已清空,所有线程都执行结束,线程池的生命周期到此结束。

5.关闭线程池

关闭线程池有两个方法,shutdown()和shutdownNow(),下面分别看一下这两个方法的实现。

shutdown()的实现

shutdown()方法将线程池运行状态设置为SHUTDOWN,此时线程池不会接受新的任务,但会处理阻塞队列中的任务。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oJwdmr1v-1596472010519)(21%E7%BA%BF%E7%A8%8B%E6%B1%A0%E7%9A%84%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86_md_files/image_20200803214006.png?v=1&type=image&token=V1:VpO0ku5Lkp17ZQbRxMzmqpD3UaqfFpZSKASJe-wkGq8)]
shutdown()方法首先会检查是否具有shutdown的权限,然后设置线程池的运行状态为SHUTDOWN,之后中断所有空闲的worker,再调用onShutdown()钩子方法,最后尝试终止线程池。

shutdown()方法调用了interruptIdleWorkers()方法中断所有空闲的worker,其实现如下。
在这里插入图片描述

shutdownNow()的实现

shutdownNow()方法将线程池运行状态设置为STOP,此时线程池不会接受新任务,也不会处理阻塞队列中的任务,并且中断正在运行的任务。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O1ff4L2E-1596472010522)(21%E7%BA%BF%E7%A8%8B%E6%B1%A0%E7%9A%84%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86_md_files/image_20200803214136.png?v=1&type=image&token=V1:iYgHxwQ-q5cjaMCV6EZtBFGsg8m8I1Yl5ng866GY3ME)]
shutdownNow()方法与shutdown()方法相似,不同之处在于,前者设置线程池的运行状态为STOP,之后中断所有的worker(并非只是空闲的worker),尝试终止线程池之后,返回任务缓存队列中等待执行的任务列表。

shutdownNow()方法调用了interruptWorkers()方法中断所有的worker(并非只是空闲的worker),其实现如下。
在这里插入图片描述

五.总结

至此,我们已经阅读了线程池框架的核心类ThreadPoolExecutor类的大部分源码,由衷地赞叹这个类很多地方设计的巧妙之处:

  • 将线程池的运行状态和工作线程数量打包在一起,并使用了大量的位运算;

  • 使用CAS操作更新线程控制状态ctl,确保对ctl的更新是原子操作;

  • 内部类Worker类继承了AQS并且实现了Runnable接口,实现了一个自定义的同步器,实现了不可重入锁;

  • 使用while循环自旋地从任务缓存队列中获取任务并执行,实现了线程复用机制

  • 调用interrupt()方法中断线程,但注意该方法并不能直接中断线程的运行,只是发出了中断信号,配合BlockingQueue的take(),poll()方法的使用,打断线程的阻塞状态;

其实,线程池的本质就是生产者消费者模式,线程池的调用者不断向线程池提交任务,线程池里面的工作线程不断获取这些任务并执行(从任务缓存队列获取任务或者直接执行任务)。

附加内容

常见的线程池类型

  1. SingleThreadExecutor()

单个线程的线程池,即线程池中每次只有一个线程工作,单线程串行执行任务。即创建一个单线程的线程池。这个线程池只有一个核心线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

  1. FixedThreadExecutor(n)

固定数量的线程池,每提交一个任务就是一个线程,直到达到线程池的最大数量,然后后面进入等待队列直到前面的任务完成才继续执行。即使用的Thread对象的数量是有限的,如果提交的任务数量大于限制的最大线程数,那么这些任务讲排队,然后当有一个线程的任务结束之后,将会根据调度策略继续等待执行下一个任务。

  1. CacheThreadExecutor(推荐使用)

可缓存线程池,当线程池大小超过了处理任务所需的线程,那么就会回收部分空闲(一般是60秒无执行)的线程,当有任务来时,又智能的添加新线程来执行。该线程池比较适合没有固定大小并且比较快速就能完成的小任务,它将为每个任务创建一个线程。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

  1. ScheduleThreadExecutor

大小无限制的线程池,支持定时和周期性的执行线程。即核心线程池固定,大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

workQueue用来保存等待被执行的任务的阻塞队列. 在JDK中提供了如下阻塞队列:

  • (1) ArrayBlockingQueue:基于数组结构的有界阻塞队列,按FIFO排序任务;

  • (2) LinkedBlockingQuene:基于链表结构的阻塞队列,按FIFO排序任务,吞吐量通常要高于ArrayBlockingQuene;

  • (3) SynchronousQuene:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQuene;

  • (4) priorityBlockingQuene:具有优先级的无界阻塞队列;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值