线程池解析
1. 核心参数
核心线程数,最大线程数,非核心线程生存时间,任务队列,线程工厂,任务溢出策略。
2.线程池状态
1.RUNNING:线程池创建完毕后进入运行中
2.SHUTDOWN:执行shutDown方法,不再接收任务,但会处理现有的任务包括队列中的任务
4.STOP:执行shutDownNow方法,不再接收任务,尝试取消现有的任务
5.TINYING:任务全部终止,执行钩子函数
6.TERMINATED:线程池终止
3.线程池任务执行流程
1.当核心线程数未满,创建核心线程执行任务
2.当核心线程已满,任务队列未满,将任务存入任务队列
3.当任务队列已满,最大线程数未满,创建非核心线程执行任务
4.当最大线程数已满,采用溢出策略处理任务
4.任务溢出策略
1.抛出异常 2.直接丢弃 3.抛弃下一个要被执行的任务,将新任务放入队列 4.交给线程池调用方处理
5.异常处理
1.任务内部try/catch
2.通过Future.get方法接收抛出的异常,在外部处理
3.为线程池的每个工作线程设置异常处理函数,且使用execute提交任务。需要自定义线程工厂
4.重写ThreadPoolExecutor#afterExecute方法,在其中处理异常
public class ExecutorTest { static ExecutorService commonPool = Executors.newCachedThreadPool(); /** * 自定义了工作工厂,设置了生产线程的异常处理机制 */ static ExecutorService unCaughtExceptionHandlePool = Executors.newCachedThreadPool((Runnable runnable) -> { Thread thread = new Thread(runnable); thread.setUncaughtExceptionHandler((Thread t, Throwable e) -> System.out.println("捕获了异常" + e.getMessage())); return thread; }); /** * 自定义了线程池的异常处理机制 */ static ThreadPoolExecutor afterExecuteHandleExceptionPool = new ThreadPoolExecutor(1, 1, 10, TimeUnit.DAYS, new LinkedBlockingDeque<>()) { @Override protected void afterExecute(Runnable r, Throwable t) { super.afterExecute(r, t); if (t == null && r instanceof Future<?>) { try { ((Future<?>) r).get(); } catch (InterruptedException e) { t = e; } catch (ExecutionException e) { t = e.getCause(); } } if (t != null) { System.out.println("捕获了异常" + t.getMessage()); } } }; public static void main(String[] args) { System.out.println("----使用Future#get捕获异常----"); handleException1(); System.out.println("----自定义线程异常处理机制-----"); handleException2(); System.out.println("----自定义线程池异常处理机制----"); handleException3(); shotDown(); } public static void handleException3() { afterExecuteHandleExceptionPool.submit(() -> System.out.println(3 / 0)); } public static void handleException2() { unCaughtExceptionHandlePool.execute(() -> System.out.println(3 / 0)); //不能使用submit } public static void handleException1() { Future<?> task = commonPool.submit(() -> System.out.print(3 / 0)); try { task.get(); } catch (Exception e) { System.out.println(e.getMessage() + e); } commonPool.shutdown(); } private static void shotDown() { commonPool.shutdown(); unCaughtExceptionHandlePool.shutdown(); afterExecuteHandleExceptionPool.shutdown(); } }
注意,使用submit的话,会把异常封装成结果,只有通过Future.get捕获
6.线程池的工作队列
1.ArrayBlockingQueue:基于数组实现,有界
2.LinkedBlockingQueue:基于链表实现,不设置大小的情况下是无界队列。无界队列可能产生内存溢出
3.DelayQueue:延迟队列,可定时和周期执行
4.PriorityBlockingQueue:自定义优先级
5.SynchronousQueue:没有存储任务的数据结构,当生产消息并放入队列时,会阻塞直到有消费者请求消费
7.基础线程池
Executors中定义了几种常用的线程池:
1.newFixedThreadPool
固定大小,不可扩容,使用无界队列,当线程全部用完时,放入任务队列,可能产生OOM。长期占用稳定数量的CPU,适合执行长期任务
2.newCachedThreadPool
初始大小为0,使用同步队列,线程空闲时间60秒。收到任务后,有空闲线程让空闲线程执行,否则新建线程执行。吞吐量高,当任务执行时间长时可能耗尽CPU资源。适合高并发小任务
3.newSingleThreadExecutor
单个处理线程,不可扩容,使用无界队列。合适处理串行任务,任务不能相互依赖否则引起饥饿
4.newScheduledThreadPool
使用延迟队列。支持延迟和周期执行
public class newScheduledThreadPoolTest { static ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10); public static void main(String[] args) { //延迟1秒执行 System.out.println(System.currentTimeMillis() + "延迟任务执行开始"); scheduledExecutorService.schedule(() -> { System.out.println(System.currentTimeMillis()+ "延迟任务执行完成"); }, 1, TimeUnit.SECONDS); //按照上次任务的开始时间计算下次任务的开始时间 //首次执行不延迟,周期1秒 scheduledExecutorService.scheduleAtFixedRate(() -> { System.out.println(System.currentTimeMillis() + "周期任务1执行开始"); try { Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } System.out.println(System.currentTimeMillis() + "周期任务1执行完毕"); }, 0, 1, TimeUnit.SECONDS); //按照上次任务的结束时间计算下次任务的开始时间 //首次执行不延迟,周期1秒 scheduledExecutorService.scheduleWithFixedDelay(() -> { System.out.println(System.currentTimeMillis() + "周期任务2执行开始"); try { Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } System.out.println(System.currentTimeMillis() + "周期任务2执行完毕"); }, 0, 1, TimeUnit.SECONDS); } }
注意扩容线程会影响执行周期,确保核心线程数满足任务需求。
8. 源码解析ThreadPoolExecutor(JDK1.8)
参考:https://www.jianshu.com/p/bf4a9e0b9e60
1.内部属性
private volatile int corePoolSize; // 核心线程数,线程池在阻塞获取任务时可以保持永久存活的线程的最大值。当线程池内的线程超过此值的线程会通过poll(keepAliveTime)获取任务 private volatile int maximumPoolSize; // 线程池中允许的最大的线程数,这里使用volatile修饰,保证多线程下的可见性 private volatile long keepAliveTime; // Woker从workQueue获取任务的最大等待时间,超过这个时间后,worker会被回收掉(run方法执行完毕,线程不可复生) private final BlockingQueue<Runnable> workQueue; // 提交的任务的排队队列,这是一个接口,通过不同的策略实现不同的线程池机制 private int largestPoolSize; // 线程池中最大的pool size,只会增加不会减少,其是一个统计信息 private final HashSet<Worker> workers = new HashSet<Worker>(); // 内部运行的Worker存放的地方,通过mainLock保证线程安全 private final ReentrantLock mainLock = new ReentrantLock(); //内部的一个独占锁,主要保证线程池的一些统计信息(最大的线程数、完成的任务数)和worker添加到集合的安全性 private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); //线程安全类型,最高位为符号位,次高3位为状态值,低28位为当前的线程数 private volatile boolean allowCoreThreadTimeOut; // 是否允许核心线程从阻塞队列获取任务时销毁。默认为false private volatile ThreadFactory threadFactory; // 内部为worker提供任务执行的线程的生成工厂。我们通过自定义的工厂来使得业务日志更为清晰或者执行不同的业务逻辑 private volatile RejectedExecutionHandler handler; // 拒绝策略,默认拒绝策略为抛出异常。线程池的拒绝策略是策略模式在JDK中的一个应用点。可以自定义拒绝策略,在生产者的速度远远大于消费者时将超出的任务持久化到外部存储。
2.重要的内部类
1 private final class Worker extends AbstractQueuedSynchronizer implements Runnable { 2 3 private static final long serialVersionUID = 6138294804551838833L; 4 final Thread thread; 5 Runnable firstTask; 6 volatile long completedTasks; 7 8 Worker(Runnable firstTask) { 9 setState(-1); // 初始化状态为-1,使得在执行任务代码前不能被中断 10 this.firstTask = firstTask; 11 this.thread = getThreadFactory().newThread(this); 12 } 13 14 //执行任务代码 15 public void run() { 16 runWorker(this); 17 } 18 19 protected boolean isHeldExclusively() { 20 return getState() != 0; 21 } 22 23 protected boolean tryAcquire(int unused) { 24 //请求资源,将状态设置为1 25 if (compareAndSetState(0, 1)) { 26 setExclusiveOwnerThread(Thread.currentThread()); 27 return true; 28 } 29 return false; 30 } 31 32 protected boolean tryRelease(int unused) { 33 setExclusiveOwnerThread(null); 34 //释放资源,将状态设置为0 35 setState(0); 36 return true; 37 } 38 39 public void lock() { acquire(1); } 40 public boolean tryLock() { return tryAcquire(1); } 41 public void unlock() { release(1); } 42 public boolean isLocked() { return isHeldExclusively(); } 43 //设置中断方法 44 void interruptIfStarted() { 45 Thread t; 46 //只有状态>=0才能设置中断 47 if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) { 48 try { 49 t.interrupt(); 50 } catch (SecurityException ignore) { 51 } 52 } 53 } 54 }
线程池内部封装的工作线程,继承了AQS,来对state进行管理,第8行初始状态为-1,unlock操作设置为0,lock操作设置为1。只有状态大于-1才可以设置中断。
AQS参考:https://www.cnblogs.com/walker993/p/14619231.html
3.execute方法
1 public void execute(Runnable command) { 2 if (command == null) 3 throw new NullPointerException(); 4 //线程池状态(包含内中线程数)的统计信息 5 int c = ctl.get(); 6 //当前线程数小于核心线程数 7 if (workerCountOf(c) < corePoolSize) { 8 //添加核心工作线程 9 if (addWorker(command, true)) 10 return;//添加成功则返回 11 //添加失败,说明线程池状态发生改变,需要重新读取标识位。 12 //添加失败的原因:1.线程池不再运行 2.超过了核心线程最大数 3.超过了最高上限(2^29-1) 13 c = ctl.get(); 14 } 15 16 //如果当前线程池仍在运行 则 将任务添加到任务队列 17 if (isRunning(c) && workQueue.offer(command)) { 18 int recheck = ctl.get(); 19 //添加完成,双重检查 20 //如果线程池不再运行 则 将任务从任务队列中移除 21 if (! isRunning(recheck) && remove(command)) 22 //对移除的任务调用任务拒绝策略 23 reject(command); 24 //如果线程池仍在运行,但是没有线程了,这里是核心线程数设置为0的情况 25 else if (workerCountOf(recheck) == 0) 26 //添加一个不附带初始任务的非核心线程。不用担心,这个线程后续会遍历任务队列处理任务 27 addWorker(null, false); 28 } 29 //到了这里有两类可能:1.线程池不再运行 2.任务队列满了不能再添加任务 30 //对于1.addWorker会处理这种情况,返回false 对于2.则添加非核心线程处理任务 31 //所以统一用addWorker处理 32 else if (!addWorker(command, false)) 33 //这个任务处理不了,调用任务拒绝策略 34 reject(command); 35 }
总结一下:当核心线程未满,添加核心线程处理。当核心线程已满,将任务添加到任务队列。当任务队列已满,添加非核心线程处理。一旦线程池不再运行,将任务抛弃。
4.addWorker方法
1 private boolean addWorker(Runnable firstTask, boolean core) { 2 /** 3 * 两层循环,外部循环判断线程池的状态,内层循环判断线程数并CAS修改线程池状态标识位ctl 4 */ 5 retry: 6 for (; ; ) { 7 int c = ctl.get(); 8 //线程池的运行状态 9 int rs = runStateOf(c); 10 11 /** 12 * 第二个布尔表达式有点长拎出来: 13 * (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty())含义是线程池状态是SHUTDOWN,并且任务队列还有没处理完的任务,此时还需要继续处理 14 * 综合就是,线程池的状态不再运行并且如果是SHUTDOWN状态,任务队列也已经全部处理完毕了。这个时候不需要添加工作线程处理了。 15 */ 16 if (rs >= SHUTDOWN && !(rs == SHUTDOWN && firstTask == null && !workQueue.isEmpty())) 17 return false; 18 19 for (; ; ) { 20 int wc = workerCountOf(c); 21 //如果工作线程数超过最大限制或者超过核心/非核心线程数量,不能继续添加 22 if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) 23 return false; 24 //CAS将工作线程数设置为c+1,注意不能用c.incrementAndGet(),这个方法会重新读取当前值,我们需要确保在c的基础上增加。 25 if (compareAndIncrementWorkerCount(c)) 26 //设置成功跳出整个循环 27 break retry; 28 //设置失败说明线程池状态发生了改变(1.线程池状态改变,2.线程数改变),重新读取状态 29 c = ctl.get(); 30 //1.线程池状态已经和进入循环的状态不一致了 31 if (runStateOf(c) != rs) 32 //需要重走外部循环判断线程池状态 33 continue retry; 34 35 //2.线程数量发生了改变,因为已经重新读取了线程数,只需要重走内部循环再次CAS尝试即可,这里省略的一段代码 36 } 37 } 38 39 /** 上面的只修改了状态标识位,这里真正添加工作线程 */ 40 41 boolean workerStarted = false; 42 boolean workerAdded = false; 43 ThreadPoolExecutor.Worker w = null; 44 try { 45 //创建一个工作线程,可以看到核心线程和非核心线程是不区分的 46 w = new ThreadPoolExecutor.Worker(firstTask); 47 final Thread t = w.thread; 48 if (t != null) { 49 final ReentrantLock mainLock = this.mainLock; 50 //加锁,对于workers.add这个操作是并发的。 51 mainLock.lock(); 52 try { 53 54 int rs = runStateOf(ctl.get()); 55 //线程池正在运行或者处于SHUTDOWN但是有未处理完毕的任务 56 if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) { 57 if (t.isAlive()) 58 throw new IllegalThreadStateException(); 59 //添加到工作线程队列 60 workers.add(w); 61 int s = workers.size(); 62 if (s > largestPoolSize) 63 largestPoolSize = s; 64 //设置工作线程添加成功标识 65 workerAdded = true; 66 } 67 } finally { 68 mainLock.unlock(); 69 } 70 if (workerAdded) { 71 //工作线程添加完毕,则开始启动这个线程,会调用worker的run方法 72 t.start(); 73 //设置工作线程启动成功标识 74 workerStarted = true; 75 } 76 } 77 } finally { 78 //工作线程启动异常 79 if (!workerStarted) 80 //会从工作队列中移除这个线程 81 addWorkerFailed(w); 82 } 83 return workerStarted; 84 }
总结一下:先CAS将线程池线程数标识+1,并创建新的工作线程添加进工作线程集合,因为线程集合是共享的,所以添加的动作需要加锁,最后启动工作线程。
这里可以看到,创建工作线程是不区分核心线程和非核心线程的。
5.runWorker方法
1 final void runWorker(ThreadPoolExecutor.Worker w) { 2 Thread wt = Thread.currentThread(); 3 //工作线程本身的任务 4 Runnable task = w.firstTask; 5 w.firstTask = null; 6 w.unlock(); //解锁,目的是设置状态位为0可以响应中断。初始状态为-1,只有大于等于0才能响应中断 7 boolean completedAbruptly = true; 8 try { 9 //处理本身的任务 或 任务队列中的任务 直到全部处理完毕,注意getTask是可以响应中断的 10 while (task != null || (task = getTask()) != null) { 11 /** 12 * 加锁的目的是保证任务执行的原子性。其他线程执行shutdown,这个方法会将所有 没有被中断 且 没有上锁 的工作线程设置中断状态。 13 * 这里加锁就保证了线程池在SHUTDOWN状态下不会被中断,但是下面继续看可以看到对于STOP及以上的状态还是会被设置为中断,这也是符合线程池状态的定义的 14 */ 15 w.lock(); 16 //如果线程池的状态在STOP级别及以上 且 当前线程还没被中断 17 if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted()) 18 //设置线程中断标识,这个循环的任务还是会继续处理,下个循环getTask返回null 19 wt.interrupt(); 20 try { 21 //空方法,可以自定义实现 22 beforeExecute(wt, task); 23 //记录任务执行过程中的异常 24 Throwable thrown = null; 25 try { 26 //执行任务 27 task.run(); 28 } catch (RuntimeException x) { 29 thrown = x; throw x; 30 } catch (Error x) { 31 thrown = x; throw x; 32 } catch (Throwable x) { 33 thrown = x; throw new Error(x); 34 } finally { 35 //空方法,可以在这里自定义异常处理 36 afterExecute(task, thrown); 37 } 38 } finally { 39 task = null; 40 w.completedTasks++; 41 //释放锁 42 w.unlock(); 43 } 44 } 45 //执行到这里说明已经没有任务需要处理了 46 completedAbruptly = false; 47 } finally { 48 //执行工作线程退出流程,正常退出和异常退出应该处理的逻辑是不一样的 49 //completedAbruptly取值为true说明是异常退出 50 //completedAbruptly取值为false说明是正常退出 51 processWorkerExit(w, completedAbruptly); 52 } 53 }
总结一下:每个Worker不仅会处理本身自带的任务,还会从任务队列中取出任务,直到所有任务执行完毕或者遇到异常。执行任务会加锁,保证SHUTDOWN状态下任务的完整执行。
6.getTask方法
1 private Runnable getTask() { 2 //记录当前线程获取任务是否已经超时 3 boolean timedOut = false; 4 /** 5 * 1.从任务队列中取出任务 6 * 2.退出多余线程(空闲线程/超出最大数量的线程) 7 */ 8 for (;;) { 9 int c = ctl.get(); 10 int rs = runStateOf(c); 11 12 //线程池在SHUTDOWN状态下已没有需要执行的任务 或 STOP状态及以上,这两种情况不需要再获取任务 13 if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) { 14 //CAS将线程数标识-1,这个方法里面封装了重试,代表当前线程不再处理任务 15 decrementWorkerCount(); 16 return null; 17 } 18 19 int wc = workerCountOf(c); 20 21 /** 22 * timed这个标识表示是否需要处理空闲超时,以下两者情况都需要处理超时 23 * 1.设置了核心线程也可以超时 2.当前线程数大于核心线程数 24 */ 25 boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; 26 27 /** 28 * 这个if是为了退出多余线程 29 * 如果当前线程大于最大线程数 或 需要处理已经发生的超时,这种情况下理论上应该要将多余的线程退出,但是还需要继续判断以下 30 * 当前线程数不止一个 或 任务队列已全部处理 满足其中之一 31 * 可以看到处理上还是留有余地的,为了更大程度上让更多的线程来处理任务 32 */ 33 if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) { 34 //在当前基础上CAS将工作线程数标识-1,代表当前线程不再处理任务 35 if (compareAndDecrementWorkerCount(c)) 36 return null; 37 //失败有循环重试 38 continue; 39 } 40 41 try { 42 /** 43 * 从队列中获取任务的方式,可以响应中断 44 * 1.需要处理超时,在超时时间内一直尝试获取任务,否则返回null 45 * 2.不处理超时,会一致阻塞直到获取任务 46 */ 47 Runnable r = timed ? 48 workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : 49 workQueue.take(); 50 //拿到了任务返回 51 if (r != null) 52 return r; 53 //到了这里说明在超时时间内没有获取到任务,这次尝试也已经达到超时时间了,下次循环将会处理这个超时 54 timedOut = true; 55 } catch (InterruptedException retry) { 56 //从任务队列中获取任务的两个方法可以响应中断,当线程池准备退出(shutDown和shutDownNow)时会中断所有线程 57 //这里为什么要设置成false?中断不算超时,需要将超时标识重置,为什么要重置? 58 //有一种情况,第一次循环中超时了,第二次循环超时退出的条件不满足,所以继续获取任务,这个时候timedOut=true 59 //第二次循环获取任务的时候,被中断了,这个时候还没有到超时时间,所以这里应该还原超时标识 60 timedOut = false; 61 } 62 } 63 }
总结一下:从任务队列中尝试获取任务,可响应中断。如果有超时退出的设定,使用带超时时间的poll方法,否则使用阻塞的take方法。方法会记录每次获取任务的时间,并判断超时情况下是否需要退出当前线程,需要退出则CAS将线程数-1,到了调用者runWorker方法,如果返回的任务为null,则调用processWorkerExit退出当前线程
超时或大于最大线程数不一定会退出当前线程,还需要满足当前线程总数>1或者任务队列为空
7.processWorkerExit方法
1 private void processWorkerExit(ThreadPoolExecutor.Worker w, boolean completedAbruptly) { 2 if (completedAbruptly) 3 //由任务执行异常引起的退出,还没来得及CAS将线程数-1,这里补上 4 decrementWorkerCount(); 5 6 final ReentrantLock mainLock = this.mainLock; 7 //需要操作工作线程集合,所以上锁 8 mainLock.lock(); 9 try { 10 //统计一下工作线程的处理任务数 11 completedTaskCount += w.completedTasks; 12 //从集合中移除工作线程 13 workers.remove(w); 14 } finally { 15 //释放锁 16 mainLock.unlock(); 17 } 18 //尝试退出线程池 19 tryTerminate(); 20 21 int c = ctl.get(); 22 //线程池在运行中/SHUTDOWN 23 if (runStateLessThan(c, STOP)) { 24 //如果是正常退出 25 if (!completedAbruptly) { 26 //满足线程池正常运作的最小的线程数 27 //1.核心线程不会超时,取核心线程数 28 //2.核心线程可超时,取0 29 //3.上面两个计算出来如果为0,且任务队列还有没处理完的任务,设置为1 30 int min = allowCoreThreadTimeOut ? 0 : corePoolSize; 31 if (min == 0 && ! workQueue.isEmpty()) 32 min = 1; 33 //确保删除完成后,当前线程数大于最小线程数,处理超删 34 if (workerCountOf(c) >= min) 35 return; 36 } 37 //不是正常退出 或 超删 会创建一个非核心线程补上 38 addWorker(null, false); 39 } 40 }
总结一下:将入参的工作线程从线程集合中删除,如果是异常退出,需要先CAS将线程数-1。如果删除后线程池还需要处理任务,异常退出和超删的情况需要补一个非核心线程。
8.关闭线程池
1 public void shutdown() { 2 final ReentrantLock mainLock = this.mainLock; 3 // 保证只有一个线程执行关闭流程 4 mainLock.lock(); 5 try { 6 // 安全检查 7 checkShutdownAccess(); 8 // 内部通过自旋+CAS修改线程池状态为shutdown 9 advanceRunState(SHUTDOWN); 10 // 遍历所有的worker,进行线程中断通知 11 interruptIdleWorkers(); 12 // 钩子函数 13 onShutdown(); 14 } finally { 15 mainLock.unlock(); 16 } 17 // 进行最后的整理工作 18 tryTerminate(); 19 } 20 21 22 public List<Runnable> shutdownNow() { 23 List<Runnable> tasks; 24 final ReentrantLock mainLock = this.mainLock; 25 mainLock.lock(); 26 try { 27 checkShutdownAccess(); 28 advanceRunState(STOP); 29 interruptWorkers(); 30 tasks = drainQueue(); 31 } finally { 32 mainLock.unlock(); 33 } 34 tryTerminate(); 35 //返回任务队列中的任务 36 return tasks; 37 }
注意只有一个线程执行关闭操作
9.线程池和资源竞争问题
提交到线程池中的任务最好是无关的,否则可能会因为队列问题导致饥饿死锁。
比如单线程线程池,在一个任务中提交了另一个任务,并等待这个完成。新任务被放入了任务队列,但新任务等待原始任务执行完成释放线程,这就导致了饥饿死锁。
当任务相关时使用newCachedThreadPool,会为每个任务创建线程。
10.线程池和锁
目前看到的线程池中用到的锁,一个是ThreadPoolExecutor中定义的mainLock,这个主要用来锁工作队列HashSet<Worker>,防止并发修改的问题。
另一个是Wroker,它本身继承了AQS,定义了独占访问资源的方法,这个锁用来控制工作线程本身的中断状态。但是细看内部,有两个加锁方法,一个是 lock() 另一个是 tryLock() 。两者有什么不同呢?
lock() 方法调用的是AQS框架中的acquire方法,在工作线程执行任务时使用,防止任务执行过程中被中断;而 tryLock() 是自定义的CAS操作,在shutDown和shutDownNow中用来通知工作线程中断,没有重试不保证成功。了解AQS的必然知道交给AQS的话,内部会有等待队列负责重试,并且通知完成后会自我设置中断标志,为什么在关闭线程池时不用 lock() 方法呢?还需要再了解下shutDown方法,关闭线程池的流程。
11. 自定义线程池如何设置最大线程数
1. CPU密集型:任务依赖CPU资源,需要用CPU做大量计算。如果设置线程数过多,那么会造成线程之间来回切换,反而浪费时间,所以最好设置为CPU核心数
2. IO密集型:任务执行大量IO操作,比如网络IO,磁盘IO。其特点是,线程需要等待对方将内容写入,这时候CPU是空闲的,所以可以设置多一点线程,让他们分到CPU,提高CPU使用率。通常设置为CPU核心数的两倍。
对于并发高、任务执行时间短的业务,线程池线程数设置为CPU核心数+1,减少上下文切换。
对于并发不高、任务执行时间长的业务,
如果是因为长时间IO,那么可以增加线程数
如果是因为长时间计算,那么减少线程数
对于并发高、执行时间长的业务,那这种应该先考虑从其他角度解决问题了,比如应对高并发,能否使用缓存、拆分、多实例来解决。任务执行时间长,是否考虑将同步转为异步,比如消息队列。
参考:
https://zhuanlan.zhihu.com/p/75054691