想要进阶自己的开发水平,JDK源码中一些优秀的设计必须要经常学习,哪怕不学习,应对面试的时候,还是要能够应对几招,代表自己对这些东西还是有所了解。
而线程池的源码,这块更是面试中经常被问到的东西,先试着列几个问题,看看自己对线程池的掌握程度:
创建线程池的参数有哪些,分别代表什么意思?
为什么阿里要求不能直接使用Executors工具类创建线程池?
线程池线程的数量如何配置?
一般线程池提交任务,执行任务的过程?
线程池中ctl属性的作用是什么?
线程池的状态有哪些?在什么时候下会出现?
一般线程池中有哪些未实现的空方法,可以用做线程池的扩展?
线程池中每一个具体的worker线程什么时候开始执行?执行的过程是什么?
核心线程与非核心线程在线程池中是怎么区分的?
线程池中的那个方法可以提前创建核心线程?
什么情况下worker线程会退出?
核心线程会不会退出?
由于程序异常导致的退出和线程池内部机制导致的退出有什么区别?
线程池shutdown与shutdownNow有什么区别?
对上面问题都已经了如指掌的大佬,联系我,让我表达对你的膜拜… 以上问题相对来说并不是很难,只要有认真看线程池源码,都可以找到答案。然后以后有人再问你线程池相关问题时,就可以拿出来说自己对线程池的理解,来聊聊把…
邮箱:aihe.ah@alibaba-inc.com
微信:aihehe5211
首先在开发的过程中,为什么需要线程池呢?给我们带来了那些好处
线程池是怎么创建的呢?一个是使用Executors,另外就是手动创建线程池,要了解其每个参数的含义。Executors创建线程池的话,要不就是对线程的数量没有控制,如CachedThreadPool,要不就是是无界队列,如FixedThreadPool。**对线程池数量和队列大小没有限制的话,容易导致OOM异常。**所以我们要自己手动创建线程池:
prestartAllCoreThreads()
提前创建所有的核心线程,prestartCoreThread
,提前创建一个核心线程allowCoreThreadTimeOut
的话对核心线程也生效keepAliveTime = unit.toNanos(keepAliveTime)
可以使用两个方法执行任务:
整体分为三个步骤:
中间会有一些对当前线程池的检查操作。
在代码中可以通过:Runtime.getRuntime().availableProcessors();
获取CPU数量。线程数计算公式:
N = CPU数量 U = 目标CPU使用率, 0 <= U <= 1 W/C = 等待(wait)时间与计算(compute)时间的比率 线程池数量 = N * U * (1 + W/C)
不过最简单的线程数指定方式,不需要公式的话:
CPU核数 + 1
CPU核数 * n
,耗时越久,分配线程数多一些线程池的状态主要通过ctl属性来控制,通过ctl可以计算出:
计算规则主要是利用了按位操作:
11100000000000000000000000000000 RUNNING 00000000000000000000000000000000 SHUTDOWN 00100000000000000000000000000000 STOP 01000000000000000000000000000000 TYDYING 01100000000000000000000000000000 TERMINATED 11100000000000000000000000000000 ctl初始值 11100000000000000000000000000000 ~CAPACITY private static int runStateOf(int c) { return c & ~CAPACITY; } 11100000000000000000000000000000 ctl初始值 00011111111111111111111111111111 CAPACITY private static int workerCountOf(int c) { return c & CAPACITY; } private static int ctlOf(int rs, int wc) { return rs | wc; }
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); private static final int COUNT_BITS = Integer.SIZE - 3; private static final int CAPACITY = (1 << COUNT_BITS) - 1; // runState is stored in the high-order bits 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;
关于TIDYING和TERMINATED主要有一块代码区,可以看出来TIDYING状态紧接着就是TERMINATED。
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) { try { // 默认是空方法 terminated(); } finally { ctl.set(ctlOf(TERMINATED, 0)); termination.signalAll(); } return; }
默认有三个扩展方法,可以用来做一些线程池运行状态统计,监控:
protected void beforeExecute(Thread t, Runnable r) { } // task.run方法之前执行 protected void afterExecute(Runnable r, Throwable t) { } // task执行完之后,不管有没有异常都会执行 protected void terminated() { }
默认线程池也提供了几个相关的可监控属性:
Worker类实现了Runnable方法,在成功创建Worker线程后就会调用其start方法。
w = new Worker(firstTask); final Thread t = w.thread; //理解为 w.thread = new Thread(w) if (workerAdded) { t.start(); workerStarted = true; } Worker(Runnable firstTask) { setState(-1); // inhibit interrupts until runWorker this.firstTask = firstTask; this.thread = getThreadFactory().newThread(this); }
Worker线程运行时执行runWorker方法,里面主要事情:
简单来说就是不断的从任务队列中取任务,如果取不到,那么就退出当前的线程,取到任务就执行任务。
final void runWorker(Worker w) { Thread wt = Thread.currentThread(); Runnable task = w.firstTask; w.firstTask = null; w.unlock(); // allow interrupts // 代表着Worker是否因为用户的程序有问题导致的死亡 boolean completedAbruptly = true; try { while (task != null || (task = getTask()) != null) { w.lock(); // If pool is stopping, ensure thread is interrupted; // if not, ensure thread is not interrupted. This // requires a recheck in second case to deal with // shutdownNow race while clearing interrupt if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted()) wt.interrupt(); try { beforeExecute(wt, task); Throwable thrown = null; try { task.run(); } catch (Exception x) { //... 不同的异常处理 } finally { afterExecute(task, thrown); } } finally { task = null; w.completedTasks++; w.unlock(); } } completedAbruptly = false; } finally { processWorkerExit(w, completedAbruptly); } }
实际上内部在创建线程时,并没有给线程做标记,因此无法区分核心线程与非核心线程。可以看出addWorker()方法
。
但是为什么可以保持核心线程一直不被销毁呢?
其内部主要根据当前线程的数量来处理。也可以理解为,只要当前的worker线程数小于配置的corePoolSize,那么这些线程都是核心线程。线程池根据当前线程池的数量来判断要不要退出线程,而不是根据是否核心线程
上面一个问题我们说到了内部其实不区分核心线程与非核心线程的,只是根据数量来判断是否退出线程,但是线程是如何退出的,又是如何一直处于保活状态呢?
如果配置了allowCoreThreadTimeOut,代表核心线程在配置的keepAliveTime时间内没获取到任务,会执行退出操作。也就是尽管当前线程数量小于corePoolSize也会执行退出线程操作。
workQueue.take()方法会一直阻塞当前的队列直到有任务的出现,因此如果执行的是take方法,那么当前的线程就不会退出。想要退出当前的线程,有几个条件:
timed=true
上面提到了,有两个方法:
prestartAllCoreThreads()
提前创建所有的核心线程prestartCoreThread
,提前创建一个核心线程,如果当前线程数量大于corePoolSize,则不创建如果线程是由于程序异常导致的退出,那么completedAbruptly为true,如下代码会再新建一个Worker线程。
如果线程是系统自动退出,即completedAbruptly为false的话,会根据配置判断当前可以允许的最小核心线程数量
int c = ctl.get(); if (runStateLessThan(c, STOP)) { if (!completedAbruptly) { int min = allowCoreThreadTimeOut ? 0 : corePoolSize; if (min == 0 && ! workQueue.isEmpty()) min = 1; if (workerCountOf(c) >= min) return; // replacement not needed } addWorker(null, false); }
看代码主要三个区别:
学无止境,还有很多细节,但足以打动面试官,觉得真是一个很用心的候选人呢… 希望这些能帮到你。
还有阿里内推:这边是阿里集团-淘系技术部的,总裁带头发起项目,新成立部门,业务急速扩张,目前还有大量的HC,机会多,考虑的联系我:
邮箱:aihe.ah@alibaba-inc.com
微信:aihehe5211