一、线程池是如何动态调节线程个数
为什么要动态调节线程的个数呢?
比方说我们公司在晚上7点的时候有免费的加班餐,大家可以在App上使用企业支付来免费吃饭,所以会有一个高峰期,这时候就可以把线程池的线程数提高,9点之后再降下来。
如何调节呢?
可以调用线程池的 的 如下方法:
// 设置核心线程大小
executorService.setCorePoolSize(10);
// 设置最大线程大小,为什么要设置这个呢?下面会分析
executorService.setMaximumPoolSize(10);
我们先举个例子看看结果:
ThreadPoolExecutor executorService = new ThreadPoolExecutor(2, 5, 100, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(10));
for (int i = 0; i < 15; i++) {
int a = i;
Runnable runnable = () -> {
Log.e("gggg", "任务" + (a + 1) + "开始");
try {
Thread.sleep(3_000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.e("gggg", "任务" + (a + 1) + "结束");
};
executorService.execute(runnable);
}
executorService.shutdown();
这是没有动态调节的,我们分析下需要多长时间?
我们 提交 15 个任务,每个任务耗时3秒,核心线程是 2 个,最大线程数 5个,队列大小是10, 注意 ArrayBlockingQueue
是不会扩容的,超过初始容量直接抛出异常。
分析如下:
● 首先 2个核心线程执行2个任务,
● 再次,将10个任务放入任务队列
● 然后,非核心线程3个执行3个任务
即 开始有5个线程执行5个任务,耗时3秒;
任何这5个任务执行完成,那么这5个线程回去任务队列中取5个任务执行,又耗时3秒,此时任务队列中还有5条任务;
等这5个任务执行完成的时候,这5个线程去取任务队列中的最后5个任务,又耗时3秒,可以看出这15个任务共耗时9秒。
我们看看log是不是9秒:
从log中可以看出,差不多就是9秒啦。
接下来动态调节了线程数了
ThreadPoolExecutor executorService = new ThreadPoolExecutor(2, 5, 100, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(10));
for (int i = 0; i < 15; i++) {
int a = i;
Runnable runnable = () -> {
Log.e("gggg", "任务" + (a + 1) + "开始");
try {
Thread.sleep(3_000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.e("gggg", "任务" + (a + 1) + "结束");
};
executorService.execute(runnable);
}
executorService.shutdown();
// 动态调节线程池中核心线程为10,最大线程为10
executorService.setMaximumPoolSize(10);
executorService.setCorePoolSize(10);
同样,我们分析下:
开始我们有5个线程执行5个任务,后来我们增加了5个线程,那么新增加的5个线程就去任务队列中取5条任务执行,3秒后,这10个任务执行完了,然后这10个线程又去任务队列中取任务,此时只有5个线程能取到任务并执行,又耗时3秒,所以总共耗时6秒。
我们看看结果啊:
可以看到 log上看,总共花了6秒
可以看出动态调节线程大小的效果还是很理想的,这只是个简单的例子,仅供说明,参考。
下面来结合源码分析为什么可行?
先看 setCorePoolSize
方法:
public void setCorePoolSize(int corePoolSize) {
if (corePoolSize < 0)
throw new IllegalArgumentException();
// 设置的核心线程数减去最开始设置的核心线程数
int delta = corePoolSize - this.corePoolSize;
// 重新设置核心线程数
this.corePoolSize = corePoolSize;
// 线程池中已有的线程数大于你设置的核心线程数,那么中断空闲的线程任务
// 这是什么场景呢?就是你高峰期过后,降低线程数,
// 所以线程池中的已有线程大于你设置的线程数
if (workerCountOf(ctl.get()) > corePoolSize)
interruptIdleWorkers();
else if (delta > 0) { // 如果设置的核心线程数大于之前设置的
// k 取队列中任务数和delta的最小值,为什么?
// 因为你调节线程数的目的就是为了添加线程来执行任务,
// 比方说,你之前核心线程数是2,现在设置成5,那么delta = 3,
// 如果,此时任务队列中只有2个任务了,那就完全没必要 添加3个线程去执行任务
int k = Math.min(delta, workQueue.size());
// 调用 addWorker(null, true),来创建新线程执行任务,addWorker的作用看之前的文章哈
while (k-- > 0 && addWorker(null, true)) {
// 判断任务队列中的消息是否为空,空了则跳出循环不再添加线程去执行任务。
if (workQueue.isEmpty())
break;
}
}
}
上面从源码分析了动态调节 核心线程数,那为什么同时要设置最大线程数呢 setMaximumPoolSize
? 这个其实想想也就知道了,因为线程池中核心线程不能大于最大线程总数啊。这个逻辑体现在哪里呢?其实就是上面 的 addWorker(null, true)
。
addWorker 部分代码如下:
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (; ; ) {
......
......
Worker w = null;
try {
// new 一个执行任务的 Worker
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
......
......
if (workerAdded) {
// 调用线程执行任务
t.start();
workerStarted = true;
}
}
} finally {
if (!workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
addWorker 中会 new 一个 Worker
去执行任务,Worker
会调用线程工厂创建一个线程来执行任务,Worker
如下:
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
任务的执行方法 runWorker(Worker w) 的 部分代码如下:
...
while (task != null || (task = getTask()) != null) {
...
}
...
因为我们调节核心线程数的方法是 : addWorker(null, true)
,即传的是个空任务,所以 runWorker
中的 while 循环直接就去任务队列中取任务 getTask
执行,也就是在 getTask
中判断当前线程池的线程数是否大于最大线程数的,如下:
private Runnable getTask() {
boolean timedOut = false;
for (; ; ) {
int c = ctl.get();
int rs = runStateOf(c);
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
// wc表示的是线程池中已有的线程数量,如果大于最大线程数maximumPoolSize
// 那么 wc 肯定是大于1的(因为maximumPoolSize 至少也是1吧),
// 那么会 把当前线程池中已有线程数减1,然后返回 null,
// 即通过动态调节核心线程数来执行任务也就没用了,所以同时要调用设置最大线程数的方法
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {//注3
if (compareAndDecrementWorkerCount(c))//注4
return null;
continue;
}
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();//注5
if (r != null)
return r;
timedOut = true;//注6
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
我在上面的注释中解释了,为什么要同时设置最大线程数。 设置最大线程数的方法如下:
public void setMaximumPoolSize(int maximumPoolSize) {
if (maximumPoolSize <= 0 || maximumPoolSize < corePoolSize)
throw new IllegalArgumentException();
this.maximumPoolSize = maximumPoolSize;
// 线程池中已有的线程数大于最大线程数就中断空闲的线程,这个也是调低最大线程数才会出现
if (workerCountOf(ctl.get()) > maximumPoolSize)
interruptIdleWorkers();
}
好了,动态调节的内容说完了,有问题可以留言哈。下面讲述下 线程池是怎么处理抛出异常任务的线程的。
线程抛出异常线程池是如何处理的?会影响其他执行任务的线程吗?线程池是回收还是删除这个线程呢?
我们带着这些疑问,写个demo试试就知道了。代码如下:
ThreadPoolExecutor executorService = new ThreadPoolExecutor(2, 2, 100, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(2));
Runnable runnable1 = () -> {
System.out.println("任务1开始 threadNmae = " + Thread.currentThread().getName());
try {
Thread.sleep(3_000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("任务1结束 threadNmae = " + Thread.currentThread().getName());
};
Runnable runnable2 = () -> {
System.out.println("异常任务2 start threadNmae = " + Thread.currentThread().getName());
throw new RuntimeException("我又出bug啦,哈哈哈!!!!");
};
Runnable runnable3 = () -> {
System.out.println("任务3开始 threadNmae = " + Thread.currentThread().getName());
try {
Thread.sleep(3_000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("任务3结束 threadNmae = " + Thread.currentThread().getName());
};
Runnable runnable4 = () -> {
System.out.println("任务4开始 threadNmae = " + Thread.currentThread().getName());
try {
Thread.sleep(3_000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("任务4结束 threadNmae = " + Thread.currentThread().getName());
};
executorService.execute(runnable1);
executorService.execute(runnable2);
executorService.execute(runnable3);
executorService.execute(runnable4);
executorService.shutdown();
我在任务2的时候抛出了一个异常,结果如下:
从运行结果看来 任务2抛出异常,打印错误堆栈信息,且不会影响其他执行任务的线程。
那么 线程池是怎么处理抛出异常的线程呢? 这个还得从源码中分析:
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
// 如果task你为空,或者去阻塞队列中去取任务不为空,这里的getTask 如果阻塞队列中任务为空 会阻塞当前线程
// 这里就是线程复用的核心,比方说当这个程执行完当前任务后,就去队列中取任务来执行,这就完成了线程的复用
while (task != null || (task = getTask()) != null) {
w.lock();
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 (RuntimeException x) {
// 这这里会 catch 住运行时的异常且抛出异常
thrown = x;
throw x;
} catch (Error x) {
thrown = x;
throw x;
} catch (Throwable x) {
thrown = x;
throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
// 最后会执行到这里,由于是抛出异常了,这里 completedAbruptly = true
processWorkerExit(w, completedAbruptly);
}
}
然后 看看 processWorkerExit(w, completedAbruptly)
,如下:
private void processWorkerExit(Worker w, boolean completedAbruptly) {
// 如果是线程执行的任务抛出异常,那么会把线程池中线程数减1
if (completedAbruptly) {
System.out.println("任务抛出异常,线程数减1");
decrementWorkerCount();
}
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
completedTaskCount += w.completedTasks;
workers.remove(w); // 移除任务
} finally {
mainLock.unlock();
}
tryTerminate();
int c = ctl.get();
// System.out.println("processWorkerExit" + " c = " + c);
if (runStateLessThan(c, STOP)) {
if (!completedAbruptly) {
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
if (min == 0 && !workQueue.isEmpty())
min = 1;
// 在核心线程不设置销毁时间时,如果工作线程小于核心线程数的时候那么此条件不成立,执行 下面的addWorker(null, false)来开启新的线程来处理任务
// 在调用shutDown方法后,会回收核心线程,此时线程池中线程数可能小于核心线程数,或者动态调小核心线程数
if (workerCountOf(c) >= min)
return; // replacement not needed
}
// 这行代码什么时候会走到呢?1: 线程执行的任务抛出异常
// 2. 如上面的解释 线程池中的线程小于核心线程
if (completedAbruptly){
System.out.println("任务抛出异常, 添加一个执行任务的线程");
addWorker(null, false);
}
}
}
这上面的代码就分析了,任务抛出异常线程池是如何处理的。