ThreadPoolExecutor详解

一、概述

本章将详细介绍任务在线程池中的执行情况、空闲线程的回收过程、默认提供的四种拒绝策略、线程池关闭以及个人觉得比较有意思的地方。

二、任务在线程池中的执行过程

线程池的一个重要作用是管理线程,实现线程的复用,避免反复创建和销毁线程所带来的资源消耗,我们通过new方法创建线程,start( )方法启动线程,线程在执行完run( )方法中的内容后就被回收,那么线程池中是如何创建启动线程,并保持线程数量,一直接收任务进行处理的呢?

2.1 线程池工作流程

  1 ExecutorService executor = new ThreadPoolExecutor(1, 1, 20, TimeUnit.SECONDS,
  2                 new ArrayBlockingQueue<>(2), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

以以上线程池为例,它大致的执行流程如下图:

image

2.2 execute方法

跟着上面流程,首先看提交任务。execute( )方法来自于Executor接口,该方法将任务提交与每个任务将如何运行的机制(包括线程使用的细节、调度等)分离开来,和start( )与run( )作用类似。

  1 public void execute(Runnable command) {
  2 	if (command == null)
  3 		throw new NullPointerException();
  4 	int c = ctl.get();
  5 
  6 	//当前线程数小于核心线程数,则启动一个新线程执行该任务
  7 	if (workerCountOf(c) < corePoolSize) {
  8 		if (addWorker(command, true))
  9 			return;
 10 		c = ctl.get();
 11 	}
 12 
 13 	//若当前线程数大于核心线程数,则进入阻塞队列进行排队
 14 	if (isRunning(c) && workQueue.offer(command)) {
 15 		int recheck = ctl.get();
 16 		if (! isRunning(recheck) && remove(command))
 17 			reject(command);
 18 		else if (workerCountOf(recheck) == 0)
 19 			addWorker(null, false);
 20 	}
 21 
 22 	//若排队不成功,则执行拒绝策略
 23 	else if (!addWorker(command, false))
 24 		reject(command);
 25 }
 26 
execute

该方法用于提交任务,并没有创建启动线程,我们跟进command在addWorker( )中的情况。

2.3 addWorker方法

  1 private boolean addWorker(Runnable firstTask, boolean core) {
  2 	retry:
  3 	for (;;) {
  4 		int c = ctl.get();
  5 		int rs = runStateOf(c);
  6 
  7 		if (rs >= SHUTDOWN &&
  8 			! (rs == SHUTDOWN &&
  9 			   firstTask == null &&
 10 			   ! workQueue.isEmpty()))
 11 			return false;
 12 
 13 		for (;;) {
 14 			int wc = workerCountOf(c);
 15 			if (wc >= CAPACITY ||
 16 			    //core为true表示创建核心线程,那么当前线程数>核心线程数,就不能在创建核心线程
 17 				//返回false执行其他逻辑
 18 				wc >= (core ? corePoolSize : maximumPoolSize))
 19 				return false;
 20 			if (compareAndIncrementWorkerCount(c))
 21 				break retry;
 22 			c = ctl.get();  // Re-read ctl
 23 			if (runStateOf(c) != rs)
 24 				continue retry;
 25 		}
 26 	}
 27 
 28 	boolean workerStarted = false;
 29 	boolean workerAdded = false;
 30 	Worker w = null;
 31 	try {
 32 
 33 	    //将任务交给线程
 34 		w = new Worker(firstTask);
 35 		final Thread t = w.thread;
 36 		if (t != null) {
 37 			final ReentrantLock mainLock = this.mainLock;
 38 			mainLock.lock();
 39 			try {
 40 				int rs = runStateOf(ctl.get());
 41 
 42 				if (rs < SHUTDOWN ||
 43 					(rs == SHUTDOWN && firstTask == null)) {
 44 					if (t.isAlive())
 45 						throw new IllegalThreadStateException();
 46 
 47 					//将线程添加至线程池,线程池直接维护的是一组Worker对象
 48 					workers.add(w);
 49 					int s = workers.size();
 50 					if (s > largestPoolSize)
 51 						largestPoolSize = s;
 52 					workerAdded = true;
 53 				}
 54 			} finally {
 55 				mainLock.unlock();
 56 			}
 57 			if (workerAdded) {
 58 
 59 			    //启动线程
 60 				t.start();
 61 				workerStarted = true;
 62 			}
 63 		}
 64 	} finally {
 65 
 66 		//若线程启动失败,则从线程池移除该线程
 67 		if (! workerStarted)
 68 			addWorkerFailed(w);
 69 	}
 70 	return workerStarted;
 71 }
addWorker

addWork( )主要用于创建一个新线程并将其加入线程池(HashSet)中,firstTask参数是这个新增的线程执行的第一个任务,core参数表示此次创建的是核心线程还是非核心线程。其中t.start( )启动了线程,t是Worker中的属性,但t执行的是哪个run( )方法呢?

2.4 Worker类

  1 private final class Worker
  2 	extends AbstractQueuedSynchronizer
  3 	implements Runnable
  4 {
  5 	final Thread thread;
  6 
  7 	Runnable firstTask;
  8 
  9 	volatile long completedTasks;
 10 
 11 	Worker(Runnable firstTask) {
 12 		setState(-1);
 13 		this.firstTask = firstTask;
 14 		this.thread = getThreadFactory().newThread(this);
 15 	}
 16 
 17 	public void run() {
 18 		runWorker(this);
 19 	}
 20 }
 21 
Worker

Worker实现了Runnable接口,构造函数中,它将传入的任务给firstTask,处理任务的线程是通过线程构造工厂,将Worker本身作为任务交给线程创建而成。则addWorker( )方法中t.start( )启动后执行的是Worker类中的run方法。

这里的线程构造工厂以Executors中DefaultThreadFactory为例,它最终也是通过new创建线程。

  1 static class DefaultThreadFactory implements ThreadFactory {
  2 	private static final AtomicInteger poolNumber = new AtomicInteger(1);
  3 	private final ThreadGroup group;
  4 	private final AtomicInteger threadNumber = new AtomicInteger(1);
  5 	private final String namePrefix;
  6 
  7 	DefaultThreadFactory() {
  8 		SecurityManager s = System.getSecurityManager();
  9 		group = (s != null) ? s.getThreadGroup() :
 10 							  Thread.currentThread().getThreadGroup();
 11 		namePrefix = "pool-" +
 12 					  poolNumber.getAndIncrement() +
 13 					 "-thread-";
 14 	}
 15 
 16 	public Thread newThread(Runnable r) {
 17 		Thread t = new Thread(group, r,
 18 							  namePrefix + threadNumber.getAndIncrement(),
 19 							  0);
 20 		if (t.isDaemon())
 21 			t.setDaemon(false);
 22 		if (t.getPriority() != Thread.NORM_PRIORITY)
 23 			t.setPriority(Thread.NORM_PRIORITY);
 24 		return t;
 25 	}
 26 }
 27 
DefaultThreadFactory

2.5 runWorker方法

  1 final void runWorker(Worker w) {
  2     //当前执行线程
  3      Thread wt = Thread.currentThread();
  4      Runnable task = w.firstTask;
  5      w.firstTask = null;
  6      w.unlock(); // allow interrupts
  7      boolean completedAbruptly = true;
  8      try {
  9 	  //第一个任务执行后,通过getTask取队列中任务
 10 	   while (task != null || (task = getTask()) != null) {
 11 		w.lock();
 12 		if ((runStateAtLeast(ctl.get(), STOP) ||
 13 			 (Thread.interrupted() &&
 14 		            runStateAtLeast(ctl.get(), STOP))) &&
 15 				!wt.isInterrupted())
 16 			wt.interrupt();
 17 		try {
 18 			beforeExecute(wt, task);
 19 			Throwable thrown = null;
 20 			try {
 21 			    //提交的任务实际在此执行
 22 				task.run();
 23 			} catch (RuntimeException x) {
 24 				thrown = x; throw x;
 25 			} catch (Error x) {
 26 				thrown = x; throw x;
 27 			} catch (Throwable x) {
 28 				thrown = x; throw new Error(x);
 29 			} finally {
 30 				afterExecute(task, thrown);
 31 			}
 32 		} finally {
 33 			task = null;
 34 			w.completedTasks++;
 35 			w.unlock();
 36 		}
 37 	  }
 38 	       completedAbruptly = false;
 39          } finally {
 40 	       processWorkerExit(w, completedAbruptly);
 41          }
 42 }
 43 
runWorker

2.6 任务执行过程小结

提交第一个任务时,调用addWorker( )将任务保存给Worker,同时通过线程构造工厂,以Worker本身为参数创建新线程。将该线程加入线程池,启动线程,线程将执行Worker中的run( )方法,run( )方法实际调用runWorker( )方法去执行保存在Worker中的任务。

任务执行完成后,线程就会被回收,线程池如何实现线程复用的呢?

2.7 getTask方法

在runWorker方法中,当前线程执行完第一次提交的任务后,会进行while循环,通过getTask从阻塞队列中取任务再执行。

  1 private Runnable getTask() {
  2 	boolean timedOut = false; // Did the last poll() time out?
  3 
  4 	for (;;) {
  5 		int c = ctl.get();
  6 		int rs = runStateOf(c);
  7 
  8 		if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
  9 			decrementWorkerCount();
 10 			return null;
 11 		}
 12 
 13 		int wc = workerCountOf(c);
 14 
 15 		//允许核心线程超时,或当前线程数>核心线程数
 16 		boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
 17 
 18 		if ((wc > maximumPoolSize || (timed && timedOut))
 19 			&& (wc > 1 || workQueue.isEmpty())) {
 20 			if (compareAndDecrementWorkerCount(c))
 21 				return null;
 22 			continue;
 23 		}
 24 
 25 		try {
 26 
 27 			//阻塞队列中的两个获取元素方法,两者都会阻塞
 28 			//poll(long timeout, TimeUnit unit) 获取并移除此队列的头部,在指定的等待时间前等待可用的元素,超时返回null
 29 			//take() 获取并移除此队列的头部,在队列中没有可用元素之前一直等待
 30 			Runnable r = timed ?
 31 				workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
 32 				workQueue.take();
 33 			if (r != null)
 34 				return r;
 35 			//
 36 			timedOut = true;
 37 		} catch (InterruptedException retry) {
 38 			timedOut = false;
 39 		}
 40 	}
 41  }
getTask

getTask( )返回任务是通过阻塞队列中的两个阻塞方法实现的,当设置允许核心线程超时,或者当前线程数大于核心线程数时,就会通过poll(long timeout, TimeUnit unit)获取,poll在等待指定时间内还没有获取到元素就会返回null,回到runWorker中执行超时或异常线程的回收操作;而take( )方法会一直阻塞,直到获取到元素。两方法允许被中断。

2.8 processWorkerExit方法

  1 private void processWorkerExit(Worker w, boolean completedAbruptly) {
  2         //completedAbruptly表示当前线程是否是异常退出
  3 		//正常结束时,在getTask执行了--操作,异常退出则需要在此执行--
  4         if (completedAbruptly)
  5             decrementWorkerCount();
  6 
  7         final ReentrantLock mainLock = this.mainLock;
  8         mainLock.lock();
  9         try {
 10             completedTaskCount += w.completedTasks;
 11             workers.remove(w);
 12         } finally {
 13             mainLock.unlock();
 14         }
 15 
 16         //有线程退出,可能是最后一个线程,尝试终止线程池
 17         tryTerminate();
 18 
 19         int c = ctl.get();
 20 
 21 
 22 		//如果此时线程池还未stop,即尝试终止线程池失败,需要确认是否添加一个Worker
 23         if (runStateLessThan(c, STOP)) {
 24             if (!completedAbruptly) {
 25                 int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
 26                 if (min == 0 && ! workQueue.isEmpty())
 27                     min = 1;
 28                 if (workerCountOf(c) >= min)
 29                     return; // replacement not needed
 30             }
 31             addWorker(null, false);
 32         }
 33     }
processWorkerExit

此方法用于清理退出的线程,将其从线程池中移除。当线程池还未终止,此时假如线程因异常退出,或线程池中无线程而任务队列还有任务,或者当前线程数量小于核心线程,就会添加一个新的线程。若线程池无任务,且没有核心线程或核心线程允许超时,则最后一个线程通过此方法结束,线程池终止。

2.9 线程复用实现小结

线程池中线程的复用是通过阻塞实现的,当线程池中线程启动后,就会进入循环不断从阻塞队列中获取任务,当阻塞队列中没有任务时,就会在poll( )或take( )方法陷入阻塞。若有线程等待超时,getTask( )会返回null,进入线程清理工作。

根据这个思路,我们也可以通过wait( )和notifyAll( )实现复用的功能。

三、ThreadPoolExecutor四种默认的拒绝策略

3.1 AbortPolicy

  1 public static class AbortPolicy implements RejectedExecutionHandler {
  2 	public AbortPolicy() { }
  3 
  4 	public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
  5 		throw new RejectedExecutionException("Task " + r.toString() +
  6 											 " rejected from " +
  7 											 e.toString());
  8 	}
  9 }
AbortPolicy

AbortPolicy是丢弃任务,抛出抛出运行时 RejectedExecutionException;

  1 ExecutorService executor = new ThreadPoolExecutor(1, 1, 20, TimeUnit.SECONDS,
  2                 new ArrayBlockingQueue<>(2), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
  3 
  4         for (int i = 0;i < 10;i++)
  5             executor.execute(new PolicyTest());//sleep 5 seconds
  6 
  7 result:Exception in thread "main" java.util.concurrent.RejectedExecutionException
AbortPolicyTest

3.2 CallerRunsPolicy

  1 public static class CallerRunsPolicy implements RejectedExecutionHandler {
  2 	public CallerRunsPolicy() { }
  3 
  4 	public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
  5 		if (!e.isShutdown()) {
  6 			r.run();
  7 		}
  8 	}
  9 }
CallerRunsPolicy

CallerRunsPolicy是直接在创建线程池的线程中运行被拒绝的任务,如果线程池已经关闭,则直接丢弃任务;

  1 ExecutorService executor = new ThreadPoolExecutor(1, 1, 2, TimeUnit.SECONDS,
  2 			new ArrayBlockingQueue<>(1), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
  3 
  4 	for (int i = 0; i < 3; i++) {
  5 		executor.execute(new PolicyTest());//print the name executed thread
  6 	}
  7 
  8 	executor.shutdown();
  9 	TimeUnit.SECONDS.sleep(10);
 10 
 11 	System.out.println("The ThreadPoolExecutor was shutdown");
 12 	executor.execute(new PolicyTest());
 13 
 14 result:I was executed by main
 15        I was executed by pool-1-thread-1
 16        I was executed by pool-1-thread-1
 17        The ThreadPoolExecutor was shutdown
CallerRunsPolicy Test

3.3 DiscardOldestPolicy

  1 public static class DiscardOldestPolicy implements RejectedExecutionHandler {
  2 	public DiscardOldestPolicy() { }
  3 
  4 	public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
  5 		if (!e.isShutdown()) {
  6 			e.getQueue().poll();
  7 			e.execute(r);
  8 		}
  9 	}
 10 }
DiscardOldestPolicy

DiscardOldestPolicy是放弃最旧的未处理请求,然后重试execute,如果线程池已关闭,则丢弃该任务;

  1 public static void main(String[] args) throws InterruptedException {
  2 	ExecutorService executor = new ThreadPoolExecutor(1, 1, 30, TimeUnit.SECONDS,
  3 			new ArrayBlockingQueue<>(2), Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardOldestPolicy());
  4 
  5 	for (int i = 0; i < 3; i++) {
  6 		executor.execute(new PolicyTest());//打印任务创建的时间和执行的线程 
  7 		TimeUnit.SECONDS.sleep(1);
  8 	}
  9 
 10 	System.out.println("Runnables in queue:");
 11 	BlockingQueue<Runnable> queue = ((ThreadPoolExecutor) executor).getQueue();
 12 	for (Runnable r: queue){
 13 		System.out.println(r.toString());//打印任务创建时间
 14 	}
 15 
 16 	TimeUnit.SECONDS.sleep(1);
 17 	System.out.println("-------execute a new runnable-------");
 18 	executor.execute(new PolicyTest());
 19 
 20 	System.out.println("New runnables in queue:");
 21 	BlockingQueue<Runnable> queue2 = ((ThreadPoolExecutor) executor).getQueue();
 22 	for (Runnable r: queue2){
 23 		System.out.println(r.toString());
 24 	}
 25 }
 26 
 27 result: I was created at 15:57:04.667 and execute by pool-1-thread-1
 28         Runnables in queue:
 29         I was created at 15:57:05.669
 30         I was created at 15:57:06.669
 31 -------execute a new runnable-------
 32         New runnables in queue:
 33         I was created at 15:57:06.669
 34         I was created at 15:57:08.672
 35         I was created at 15:57:06.669 and execute by pool-1-thread-1
 36         I was created at 15:57:08.672 and execute by pool-1-thread-1
 37 //执行新任务后,最先创建的任务被删除,新任务重新被execute
DiscardOldestPolicyTest

3.4 DiscardPolicy

默认情况下它将丢弃被拒绝的任务。

  1 public static class DiscardPolicy implements RejectedExecutionHandler {
  2 	public DiscardPolicy() { }
  3 
  4 	public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
  5 	}
  6 }
DiscardPolicy

四、关闭线程池

4.1 ctl变量

线程池用ctl变量表示了两部分信息,线程池的状态和当前线程数量。

  1    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
  2     private static final int COUNT_BITS = Integer.SIZE - 3;
  3     private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
  4 
  5     // runState is stored in the high-order bits
  6     private static final int RUNNING    = -1 << COUNT_BITS;
  7     private static final int SHUTDOWN   =  0 << COUNT_BITS;
  8     private static final int STOP       =  1 << COUNT_BITS;
  9     private static final int TIDYING    =  2 << COUNT_BITS;
 10     private static final int TERMINATED =  3 << COUNT_BITS;
 11 
 12     // Packing and unpacking ctl
 13     private static int runStateOf(int c)     { return c & ~CAPACITY; }
 14     private static int workerCountOf(int c)  { return c & CAPACITY; }
 15     private static int ctlOf(int rs, int wc) { return rs | wc; }
ctl

通过位运算,线程池将一个32位的int类型的数的高3位用于表示线程池状态,低29位用于表示当前线程数量。因此线程池线程最大值为2^29-1,即Capacity;而-1、0、1、2、3分别左移29位为111,000,001,010,011(后29位都是0),用来表示5种状态。

runStateOf( )方法通过将capacity取反(高三位全为1,低29位全为0),进行&操作就可以获得高3位;workerCountOf( )直接&,就可以保留下低29位;ctlOf( )将runState | workerCount就可以将两部分合并。

4.2 线程池状态

在追踪任务执行流程中,几个主要方法里存在大量的状态判断,线程池有五个状态,不同状态下的工作权限不同。

  • RUNNING:线程池创建就处于RUNNING状态,该状态下能够接收新任务,以及对已添加的任务进行处理;
  • SHUTDOWN:如果调用了shutdown( )方法,则线程池处于SHUTDOWN状态,该状态不接收新任务,但能处理已添加的任务;
  1 public static void main(String[] args) throws InterruptedException {
  2 	ExecutorService executor = new ThreadPoolExecutor(1, 1, 30, TimeUnit.SECONDS,
  3 			new ArrayBlockingQueue<>(2), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
  4 
  5 	for (int i = 0;i < 3;i++) {
  6 		executor.execute(new PolicyTest());//打印任务开始标志(时间和执行线程),sleep2秒,打印任务完成情况
  7 		TimeUnit.SECONDS.sleep(1);
  8 	}
  9 	BlockingQueue<Runnable> queue = ((ThreadPoolExecutor) executor).getQueue();
 10 	for (Runnable r: queue)
 11 		System.out.println(r);
 12 
 13 	System.out.println(executor.isShutdown());
 14 	executor.shutdown();
 15 	System.out.println(executor.isShutdown());
 16 
 17 	TimeUnit.SECONDS.sleep(1);
 18 	executor.execute(new PolicyTest());
 19 }
 20 
 21 result:I was created at 10:13:03.371 and execute by pool-1-thread-1
 22 		I was created at 10:13:04.371
 23 		I was created at 10:13:05.371
 24 		false
 25 		true
 26 		Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task I was created at 10:13:07.373
 27 		I was created at 10:13:03.371 and finish by pool-1-thread-1
 28 		I was created at 10:13:04.371 and execute by pool-1-thread-1
 29 		I was created at 10:13:04.371 and finish by pool-1-thread-1
 30 		I was created at 10:13:05.371 and execute by pool-1-thread-1
 31 		I was created at 10:13:05.371 and finish by pool-1-thread-1
shutdownTest

以上例,虽然已经执行了shutdown( )方法,isShutdown( )返回了true,但是线程池并没有立即停止,而是继续执行完正在执行和队列中已存在任务,直到任务完成以后才退出,不过shutdown( )以后对新提交的任务执行了拒绝策略。

  • STOP:如果调用shutdownNow( )方法,则线程池处与于STOP状态,此状态不接收新任务,不处理已添加的任务,并且会尝试中断正在处理的任务;
  1 //以上例,将shutdown( )换成shutdownNow( ),打印任务队列中元素,并打印shutdownNow返回值
  2 result:I was created at 10:40:32.554 and execute by pool-1-thread-1
  3 		任务队列中任务:
  4 		I was created at 10:40:33.555
  5 		I was created at 10:40:34.555
  6 		false
  7 		shutdownNow返回删除的任务:
  8 		I was created at 10:40:33.555
  9 		I was created at 10:40:34.555
 10 		true
 11 		Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task I was created at 10:40:36.558
shutdownNowTest

将上例shutdown( )换成shutdownNow( ) ,线程池立即退出,正在执行的任务被中断,任务队列中的任务被删除,新提交的任务执行了拒绝策略。

  • TIDYING:当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为TIDYING状态,TIDYING状态时,会执行钩子函数terminated( );
  • TERMINATED:当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态。

4.3 shutdown和shutdownNow方法

上面测试了调用shutdown( )和shutdownNow( )方法线程池对任务会有不同的处理方式,我们从源码继续剖析。

  1 public void shutdown() {
  2 	final ReentrantLock mainLock = this.mainLock;
  3 	mainLock.lock();
  4 	try {
  5 		checkShutdownAccess();
  6 		//设置状态
  7 		advanceRunState(SHUTDOWN);
  8 		interruptIdleWorkers();
  9 		onShutdown(); // hook for ScheduledThreadPoolExecutor
 10 	} finally {
 11 		mainLock.unlock();
 12 	}
 13 	//尝试终止线程池
 14 	tryTerminate();
 15 }
 16 
 17 public List<Runnable> shutdownNow() {
 18 	List<Runnable> tasks;
 19 	final ReentrantLock mainLock = this.mainLock;
 20 	mainLock.lock();
 21 	try {
 22 		checkShutdownAccess();
 23 		advanceRunState(STOP);
 24 		interruptWorkers();
 25 		//复制任务到List
 26 		tasks = drainQueue();
 27 	} finally {
 28 		mainLock.unlock();
 29 	}
 30 	tryTerminate();
 31 	return tasks;
 32 }
shutdown and shutdownNow

从源码看两者主要步骤有设置状态,interrupt线程,尝试终结线程池。shutdown( )将状态设为SHUTDOWN,shutdownNow( )将状态设置为STOP。advanceRunState( )方法,如果当前状态大于你要设置的状态则设置失败,因为线程池状态表示是递增的,如果当前状态数值更大,说明已经经历过此状态),两者主要不同在于分别使用了interruptidleWorkers( )和interruptWorkers( )方法interrupt线程,最后两者都尝试终结线程池。这两个方法均不会阻塞,直接返回,同时它们都是通过interrupt( )方法中断线程,而interrupt并不是真正意义上的中断线程,它只是设置了一个中断标志(信号),只有当线程对该标志进行响应(Thread.interrupted( )、Thread.isInterrupted( ))才会退出任务,对于阻塞的线程,调用中断时,线程将会立刻退出阻塞状态并抛出 InterruptedException 异常。因此接收不到interrupt信号或没对收到信号进行处理的任务,可能永远也不会结束。

  1 public static void main(String[] args) throws InterruptedException {
  2         ExecutorService executor = new ThreadPoolExecutor(1, 1, 30, TimeUnit.SECONDS,
  3                 new ArrayBlockingQueue<>(2), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
  4 
  5         executor.execute(new NoneInterruptFlag());
  6         System.out.println(((ThreadPoolExecutor) executor).getActiveCount() + " active count");
  7         executor.shutdownNow();
  8         TimeUnit.SECONDS.sleep(1);
  9         System.out.println(executor.isShutdown());
 10         System.out.println(((ThreadPoolExecutor) executor).getActiveCount() + " active count");
 11     }
 12 
 13 static class NoneInterruptFlag implements Runnable {
 14 
 15 	@Override
 16 	public void run() {
 17 		while (true) {
 18 			//do something
 19 		}
 20 	}
 21 }
NoneInterruptTask

上面程序,即使调用shutdown( )也无法终止线程,interrupt只是发出了一个中断信号,而run方法中没有接收这个信号,也就无法终止。或者如下抓住了异常,但并没有进行处理,仍会继续执行。

  1 @Override
  2 public void run() {
  3    while (true) {
  4 	try {
  5            TimeUnit.SECONDS.sleep(10);
  6            System.out.println("I am still running!");
  7 	} catch (InterruptedException e) {
  8 	   e.printStackTrace();
  9 	}
 10     }
 11 }
notDealInterruptException

4.4 interruptIdleWorkers和interruptWorkers方法

  1 private void interruptIdleWorkers() {
  2 	interruptIdleWorkers(false);
  3 }
  4 
  5 private void interruptIdleWorkers(boolean onlyOne) {
  6 	final ReentrantLock mainLock = this.mainLock;
  7 	mainLock.lock();
  8 	try {
  9 		for (Worker w : workers) {
 10 			Thread t = w.thread;
 11 			//对线程尝试获取锁
 12 			if (!t.isInterrupted() && w.tryLock()) {
 13 				try {
 14 					t.interrupt();
 15 				} catch (SecurityException ignore) {
 16 				} finally {
 17 					w.unlock();
 18 				}
 19 			}
 20 			if (onlyOne)
 21 				break;
 22 		}
 23 	} finally {
 24 		mainLock.unlock();
 25 	}
 26 }
interruptIdleWorkers

interruptIdleWorkers对所有线程进行遍历时,进行了tryLock( )操作,只有加锁成功的线程才会执行interrupt( )。什么情况线程才能被加锁呢?回到runWorker( ),线程在执行任务之前有w.lock( )操作,因此只有没有执行任务的线程会tryLock( )成功,执行interrupt( )。

  1 private void interruptWorkers() {
  2 	final ReentrantLock mainLock = this.mainLock;
  3 	mainLock.lock();
  4 	try {
  5 		for (Worker w : workers)
  6 			w.interruptIfStarted();
  7 	} finally {
  8 		mainLock.unlock();
  9 	}
 10 }
 11 
 12 
 13 void interruptIfStarted() {
 14 	Thread t;
 15 	if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
 16 		try {
 17 			t.interrupt();
 18 		} catch (SecurityException ignore) {
 19 		}
 20 	}
 21 }
interruptWorkers

interruptWorkers( )调用了Worker类中的interruptIfStarted( )方法,只有Worker的state大于等于0时才会执行interrupt( )。Worker实现了AQS类,状态 1 表示锁被占用,0 表示锁被释放,因此大于等于 0 就代表了所有的线程,即线程池内全部线程都会执行interrupt( )。

4.5 tryTerminate方法

处理完线程池内线程,还需要处理任务队列。shutdownNow( )调用了drainQueue( )方法将队列中的元素复制到List,删除队列中的元素。

  1 tasks = drainQueue();
  2 
  3 private List<Runnable> drainQueue() {
  4 	BlockingQueue<Runnable> q = workQueue;
  5 	List<Runnable> taskList = new ArrayList<Runnable>();
  6 	q.drainTo(taskList);
  7 	//若q中还有元素表示drainTo出错,将出错元素重新加入list
  8 	if (!q.isEmpty()) {
  9 		for (Runnable r : q.toArray(new Runnable[0])) {
 10 			if (q.remove(r))
 11 				taskList.add(r);
 12 		}
 13 	}
 14 	return taskList;
 15 }
 16 
 17 /*
 18  *移除此队列中所有可用的元素,并将它们添加到给定 collection 中。
 19  *在试图向 collection c 中添加元素没有成功时,可能导致在抛出相关异常时,
 20  *元素会同时在两个 collection 中出现,或者在其中一个 collection 中出现,
 21  *也可能在两个 collection 中都不出现。如果试图将一个队列放入自身队列中,
 22  *则会导致 IllegalArgumentException 异常。此外,如果正在进行此操作时修
 23  *改指定的 collection,则此操作行为是不确定的。
 24  */
 25 public int drainTo(Collection<? super E> c) {
 26 	checkNotNull(c);
 27 	if (c == this)
 28 		throw new IllegalArgumentException();
 29 	final Object[] items = this.items;
 30 	final ReentrantLock lock = this.lock;
 31 	lock.lock();
 32 	try {
 33 		int i = takeIndex;
 34 		int n = 0;
 35 		int max = count;
 36 		while (n < max) {
 37 			c.add(this.<E>cast(items[i]));
 38 			items[i] = null;
 39 			i = inc(i);
 40 			++n;
 41 		}
 42 		if (n > 0) {
 43 			count = 0;
 44 			putIndex = 0;
 45 			takeIndex = 0;
 46 			notFull.signalAll();
 47 		}
 48 		return n;
 49 	} finally {
 50 		lock.unlock();
 51 	}
 52 }
drainQueue

shutdown( )没有处理队列,而是在执行tryTerminate( )方法时进行了判断。文档注释为:在以下情况将线程池变为TERMINATED终止状态,shutdown 且 正在运行的worker 和 workQueue队列都empty, stop 且没有正在运行的worker。 这个方法必须在任何可能导致线程池终止的情况下被调用,如: 减少worker数量;shutdown时从queue中移除任务。

  1 final void tryTerminate() {
  2 	for (;;) {
  3 		int c = ctl.get();
  4 
  5 		//如果是线程池正running或正在关闭、已经关闭(tidying、terminated)
  6 		//或处于shutdown状态且任务队列非空,直接返回
  7 		if (isRunning(c) ||
  8 			runStateAtLeast(c, TIDYING) ||
  9 			(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
 10 			return;
 11 
 12 		//此时线程池处于stop 或 shutdown且队列为空 的状态
 13 		//如果还有线程,就尝试中断一个线程,返回
 14 		if (workerCountOf(c) != 0) { // Eligible to terminate
 15 			interruptIdleWorkers(ONLY_ONE);
 16 			return;
 17 		}
 18 
 19 		final ReentrantLock mainLock = this.mainLock;
 20 		mainLock.lock();
 21 		try {
 22 			if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
 23 				try {
 24 					terminated();
 25 				} finally {
 26 					ctl.set(ctlOf(TERMINATED, 0));
 27 
 28 					//唤醒等待线程池终止的线程,awaitTermination( )
 29 					termination.signalAll();
 30 				}
 31 				return;
 32 			}
 33 		} finally {
 34 			mainLock.unlock();
 35 		}
 36 		// else retry on failed CAS
 37 	}
 38 }
 39 
tryTerminate

4.6 awaitTermination方法

  1 public boolean awaitTermination(long timeout, TimeUnit unit)
  2 	throws InterruptedException {
  3 	//将超时时间转化为纳秒单位
  4 	long nanos = unit.toNanos(timeout);
  5 	final ReentrantLock mainLock = this.mainLock;
  6 	mainLock.lock();
  7 	try {
  8 		for (;;) {
  9 			//若状态是terminated,返回true
 10 			if (runStateAtLeast(ctl.get(), TERMINATED))
 11 				return true;
 12 			//超时返回false
 13 			if (nanos <= 0)
 14 				return false;
 15 			//实现阻塞
 16 			nanos = termination.awaitNanos(nanos);
 17 		}
 18 	} finally {
 19 		mainLock.unlock();
 20 	}
 21 }
awaitTermination

上面说到shutdown( )和shutdownNow( )方法都是不阻塞方法,awaitTermination( )也用于终结线程池,它是一个阻塞方法。该方法并没有对线程池中线程或状态做任何修改,仅仅是在超时后,返回一个结果。

问题一,如果在该方法阻塞期间,另一个线程shutdown了线程池,该方法还会继续阻塞吗?

  1 public static void main(String[] args) throws InterruptedException {
  2 	ExecutorService executor = new ThreadPoolExecutor(1, 1, 10, TimeUnit.SECONDS,
  3 			new ArrayBlockingQueue<>(2), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
  4 
  5 	executor.execute(new PolicyTest());//sleep 10 seconds
  6 
  7 	new Thread(() -> {
  8 		try {
  9 			//保证awaitTermination已阻塞
 10 			TimeUnit.SECONDS.sleep(1);
 11 			executor.shutdownNow();
 12 		} catch (InterruptedException e) {
 13 			e.printStackTrace();
 14 		}
 15 	}).start();
 16 
 17 	System.out.println("awaitTermination已阻塞");
 18 	boolean b = executor.awaitTermination(20, TimeUnit.SECONDS);
 19 	System.out.println(b);
 20 	System.out.println(executor.isTerminated());
 21 }
 22 
 23 result:I was created at 15:37:14.897 and execute by pool-1-thread-1
 24 		awaitTermination已阻塞
 25 		true
 26 		true
awaitTerminationTest1

结果该方法立刻退出了阻塞,在分析tryTerminate( )方法时,出现了termination.signalAll()调用,该方法用于唤醒等待线程池终止的线程,也就是调用了awaitTermination( )方法的线程。

问题二,有没有可能线程池已经退出,awaitTermination( )依然返回false?

  1 public static void main(String[] args) throws InterruptedException {
  2 	ExecutorService executor = new ThreadPoolExecutor(1, 1, 10, TimeUnit.SECONDS,
  3 			new ArrayBlockingQueue<>(2), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
  4 
  5 	((ThreadPoolExecutor) executor).allowCoreThreadTimeOut(true);
  6 	executor.execute(new PolicyTest());//sleep 3 seconds
  7 
  8 	boolean b = executor.awaitTermination(20, TimeUnit.SECONDS);
  9 	System.out.println(b);
 10 	System.out.println(executor.isTerminated());
 11 }
 12 
 13 result:I was created at 15:26:14.046 and execute by pool-1-thread-1
 14        I was created at 15:26:14.046 and finish by pool-1-thread-1
 15        false
awaitTerminationTest2

结果线程池实际已经退出,但awaitTermination( )方法还在阻塞且最后返回false。awaitTermination( )方法是在指定时间内,线程池为terminated状态返回true,而到terminated状态必定调用了tryterminate( )且成功设置了terminated状态。但除此之外,当线程池没有核心线程或允许核心线程超时,线程池最终会通过processWorkerExit( )方法结束所有线程,线程池直接从running状态退出。

4.7 优雅关闭线程池

如果直接粗暴关掉线程池,可能正在执行的任务就突然丢失了;但有一些任务可能耗费时间太长,不可能一直等待。比较shutdown( )和shutdownNow( ),前者比较温柔,后者就比较简单粗暴了,此时可以将三个方法一起用,先进行shutdwon( ),如果在指定时间内还没有结束,才调用shutdownNow( )。

  1 executor.shutdown();
  2 try {
  3 	if (!executor.awaitTermination(10, TimeUnit.MINUTES))
  4 		executor.shutdownNow();
  5 } catch (InterruptedException e) {
  6 	executor.shutdownNow();
  7 }
elegantEnd

如果遇到类似上面while(true)的情况关闭不了,也可以通过将线程设置为守护线程,当然肯定要避免这样的情况。

posted @ 2020-08-29 17:21  Aidan_Chen  阅读(370)  评论(0编辑  收藏  举报