相关问题
1、工作流程是怎么样的。
2、核心参数。
3、任务结束之后如何恢复到核心线程数。
4、线程池运行时有一个线程报错了会怎么样。
5、如何合理设置核心线程数的大小
6、不同子线程之间怎么传递数据
7、5个核心线程数,30个任务队列,20个最大线程数,200s存活时间,一个for循环加入20个任务,
这个时候线程池中有多少个。
1、工作流程是怎么样的。
execute方法
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
* 1.如果运行的线程少于核心线程数,尝试新建一个线程作为第一个任务。
*
* 2. 如果有任务可以成功排队,那就加入队列
*
* 3. 如果无法加入队列,那么尝试添加一个新线程。
* 要是失败了就知道被关闭或包和了,因此拒绝这项任务
*/
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
2、核心参数。如何合理设置核心线程数的大小
最⼤线程数maximumPoolSize
-
核⼼线程数corePoolSize
任务队列的长度要根据核心线程数,以及系统对任务响应时间的要求有关。队列长度可以设置为 所有核心线程每秒处理任务数 * 每个任务响应时间 = 每秒任务总响应时间 ,即(corePoolSizethreadtasks)responsetime: (2010)2=400,即队列长度可设置为400。
-
活跃时间keepAliveTime
当负载降低时,可减少线程数量,当线程的空闲时间超过keepAliveTime,会自动释放线程资源。默认情况下线程池停止多余的线程并最少会保持corePoolSize个线程。
-
阻塞队列workQueue
BlockingQueue workQueue = null; workQueue = new ArrayBlockingQueue<>(5);//基于数组的先进先出队列,有界 workQueue = new LinkedBlockingQueue<>();//基于链表的先进先出队列,无界 workQueue = new SynchronousQueue<>();//无缓冲的等待队列,无界
拒绝策略RejectedExecutionHandler
可继承后进行自定义。
提供的拒绝策略有:
- AbortPolicy:直接丢弃任务,抛出异常,这是默认策略
- CallerRunsPolicy:只⽤调⽤者所在的线程来处理任务
- DiscardOldestPolicy:丢弃等待队列中最旧的任务,并执⾏当前任务
- DiscardPolicy:直接丢弃任务,也不抛出异常
- allowCoreThreadTimeout
默认情况下核心线程不会退出,可通过将该参数设置为true,让核心线程也退出。
一般说来,大家认为线程池的大小经验值应该这样设置:(其中N为CPU的个数)
- 如果是CPU密集型应用,则线程池大小设置为N+1
- 如果是IO密集型应用,则线程池大小设置为2N+1
3、任务结束之后如何恢复到核心线程数/线程池中多余的线程是如何回收的?
什么时候回收线程?
ThreadPoolExecutor回收工作线程,一条线程getTask()返回null,就会被回收。
分为两种情况
一、未调用shutdown() ,RUNNING状态下全部任务执行完成的场景
线程数量大于corePoolSize,线程超时阻塞,超时唤醒后CAS减少工作线程数,如果CAS成功,返回null,线程回收。否则进入下一次循环。当工作者线程数量小于等于corePoolSize,就可以一直阻塞了。
二、调用shutdown() ,全部任务执行完成的场景
2.1 所有线程都在阻塞
中断唤醒,进入循环,都符合第一个if判断条件,都返回null,所有线程回收。
2.2 任务还没有完全执行完
至少会有一条线程被回收。在processWorkerExit(Worker w, boolean completedAbruptly)方法里会调用tryTerminate(),向任意空闲线程发出中断信号。所有被阻塞的线程,最终都会被一个个唤醒,回收。
如何被回收的。
private void processWorkerExit(Worker w, boolean completedAbruptly) {
if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
completedTaskCount += w.completedTasks;
//这里就是回收线程的主要操作了,移除线程池对该线程的引用,使其可以被JVM正常地回收
workers.remove(w);
} finally {
mainLock.unlock();
}
tryTerminate();
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);
}
}
在runWorker()
方法中被执行
ThreadPoolExecutor是怎么回收线程的:
https://juejin.cn/post/6922069411981426702
4、线程池运行时有一个线程报错了会怎么样。
当一个线程池里面的线程异常后:
1、当执行方式是execute时,可以看到堆栈异常的输出
ThreadPoolExecutor.runWorker()方法中,task.run(),即执行我们的方法,如果异常的话会throw x;所以可以看到异常。
2、当执行方式是submit时,堆栈异常没有输出。但是调用Future.get()方法时,可以捕获到异常
ThreadPoolExecutor.runWorker()方法中,task.run(),其实还会继续执行FutureTask.run()方法,再在此方法中c.call()调用我们的方法,如果报错是setException(),并没有抛出异常。当我们去get()时,会将异常抛出。
3、不会影响线程池里面其他线程的正常执行
4、线程池会把这个线程移除掉,并创建一个新的线程放到线程池中
当线程异常,会调用ThreadPoolExecutor.runWorker()方法最后面的finally中的processWorkerExit(),会将此线程remove,并重新addworker()一个线程。
execute
的入参是Runnable, 没有返回值。任务通过execute
提交后就基本和主线程脱离关系了。
而submit
的入参可以是Callable(也可以是Runnable),并且有返回值,返回的是一个Future
对象,然后通过对象的get
方法获取任务执行的结果。
5、不同子线程之间怎么传递数据
6、5个核心线程数,30个任务队列,20个最大线程数,200s存活时间,一个for循环加入20个任务,这个时候线程池中有多少个。
结合线程池的工作原理就能知道有5个活跃线程