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());
以以上线程池为例,它大致的执行流程如下图:
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
该方法用于提交任务,并没有创建启动线程,我们跟进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 }
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实现了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
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
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( )返回任务是通过阻塞队列中的两个阻塞方法实现的,当设置允许核心线程超时,或者当前线程数大于核心线程数时,就会通过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 }
此方法用于清理退出的线程,将其从线程池中移除。当线程池还未终止,此时假如线程因异常退出,或线程池中无线程而任务队列还有任务,或者当前线程数量小于核心线程,就会添加一个新的线程。若线程池无任务,且没有核心线程或核心线程允许超时,则最后一个线程通过此方法结束,线程池终止。
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是丢弃任务,抛出抛出运行时 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
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是直接在创建线程池的线程中运行被拒绝的任务,如果线程池已经关闭,则直接丢弃任务;
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
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是放弃最旧的未处理请求,然后重试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
3.4 DiscardPolicy
默认情况下它将丢弃被拒绝的任务。
1 public static class DiscardPolicy implements RejectedExecutionHandler { 2 public DiscardPolicy() { } 3 4 public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { 5 } 6 }
四、关闭线程池
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; }
通过位运算,线程池将一个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
以上例,虽然已经执行了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
将上例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 }
从源码看两者主要步骤有设置状态,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 }
上面程序,即使调用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 }
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对所有线程进行遍历时,进行了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( )调用了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 }
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
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 }
上面说到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
结果该方法立刻退出了阻塞,在分析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
结果线程池实际已经退出,但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 }
如果遇到类似上面while(true)的情况关闭不了,也可以通过将线程设置为守护线程,当然肯定要避免这样的情况。