//java.util.concurrent.Executors java的Executors的工具类
Executors.newFixedThreadPool()
Executors.newCachedThreadPool()
Executors.newSingleThreadExecutor()
Executors.newScheduledThreadPool()
Executors.newSingleThreadScheduledExecutor()
上面的概念你都会了,那么问题来了:问你下面7个问题你扛得住吗?
请打开idea找到java.util.concurrent.ThreadPoolExecutor类,一起来撕逼一下吧(请特别注意代码中我的中文代码注释(是我笔记,也是答案))
1.引子:如下创建固定线程池后,先扔5个,隔5秒在扔11个,隔5秒再扔10个,任务执行时间假设很长30分钟,执行顺序是怎么样的?那又或者随机很长很短呢?
new ThreadPoolExecutor(5, //coresize (池核心线程数,最小运行线程数)
10, //maxsize(池允许最大线程数)
10000L, //time 这里空闲时间为10秒,超过空闲时间大于coresize的空闲线程则被销毁
TimeUnit.MILLISECONDS, //timeunit 毫秒
new ArrayBlockingQueue<Runnable>(10) //taskqueue 任务队列
);
答:
假设执行时间固定很长,扔完这26个所有线程还在忙,那场景应该是:扔进5,启动5个core线程,再扔进11,优先放队列(taskqueue=10),队列放不下,第11个在尝试启动线程6,在扔10个时候,这10个的前4个可以启动线程,直至max线程数为10,假设执行任务还是很长,一个线程都没有结束,第三次扔的10个里的第5个起线程也启动不了,则reject(runable)给用户user code处理
理由jdk源码:
//java.util.concurrent.ThreadPoolExecutor
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
//英文注释略,自行看源码...
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) { //如果小于最小(核心)线程数
if (addWorker(command, true)) //则尝试启动一个线程,并且带入这个任务作为FirstTask任务执行
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) { //有可运行线程数,则尝试将command任务加入队列
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command)) //加入任务到队列后,双重检查:如果所有运行的线程死了(取反为true),接着执行从队列里移走,刚刚加入的comand任务,并且执行拒绝操作
reject(command); //拒绝任务,抛错给user的代码(非jdk代码)
else if (workerCountOf(recheck) == 0) //加入任务到队列后,如果有可运行的线程为0了,在启动一个线程
addWorker(null, false);
}
else if (!addWorker(command, false)) //如果没有可运行的线程数,直接尝试启动一个线程,并且带入这个任务作为FirstTask任务执行
reject(command); //如果启动线程失败,则拒绝这个任务,抛错给user的代码(因为执行任务肯定也失败了)
}
2.上面三次分不同批次提交的任务都执行完了,超过了10秒(队列空闲)没有任务提交,线程数为什么(怎么会)减少为5的(coresize=5) 答案如下:
根据当前运行的线程数,如果是wc(当前运行线程数)<=coresize(5),则一直阻塞,这样线程不会被杀掉只是阻塞巧妙的利用take阻塞特性尽力保住coresise(5)个线程数
如果是wc(当前运行线程数)>coresize(5),则会使用poll方法直至超时取不到task,会设置timed=true && timedOut=true,在死循环的下一轮里会干掉存活且多余coresize(5个)的线程compareAndDecrementWorkerCount(c)
//java.util.concurrent.ThreadPoolExecutor
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
Runnable r = timed ? //假设当前线程数为10个 则wc>coresize=true,则使用poll超时拉取任务,否则小于或者等于coresize(5),则使用take函数一直阻塞,那如果被以外终止,导致小于coresize呢,这个问题问的好,见后面的问题4的保证机制(每次执行任务后校验最少线程数,不足则补数)
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true; //在死循环的下一轮里 timed=true && timedOut=true 则减少线程数,这样反复操作,使当前运行的线程数wc=10,逐渐减少至coresize=5,再追问:是否运行线程数会少于5个呢?肯定有嘛,因为中断异常,或者用户线程异常情况,都会导致小于5个?那什么机制会保证池的运行线程数wc尽量靠近coresize=5呢?
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
3.问题1里面的addWorker(null,...)启动线程了,没有传递task(command),那怎么执行任务的?
else if (workerCountOf(recheck) == 0) //加入任务到队列后,如果有可运行的线程为0了,在启动一个线程
addWorker(null, false);
答:官方英文注释有答案:
execute提交任务时候,wc(当前运行线程数)<coresize时,要新启动线程,并且新线程应该执行提交传参过来的task,也就是新线程应该创建并执行附带过来的一个任务【敲黑板:不是从任务队列取,而是直接执行,所以任务会是FIFO的观点错误】
execute提交任务时候,wc(当前运行线程数)>=coresize时,并且任务taskqueue已经满了,maxsize>wc(当前运行线程数)>coresize的话,会按需尝试启动>coresize但是小于maxsize的线程数,并且执行execute提交过来的任务【敲黑板:不是从任务队列取,而是直接执行,所以任务会是FIFO的观点错误】
空闲的线程创建方式通常是新建线程替换corethread(重启那5个核心线程数),或者替换脏线程,但是这里用词是“usually”是通常情况,那么也就是说也有不是重启corethread和replace dying workers,我推测就是直接new出来的 worker了
//java.util.concurrent.ThreadPoolExecutor
/*
* 英文注释略,自行看源码...
* @param firstTask the task the new thread should run first (or
* null if none). Workers are created with an initial first task
* (in method execute()) to bypass queuing when there are fewer
* than corePoolSize threads (in which case we always start one),
* or when the queue is full (in which case we must bypass queue).
* Initially idle threads are usually created via
* prestartCoreThread or to replace other dying workers.
* execute提交任务时候,wc(当前运行线程数)<coresize时,要新启动线程,并且新线程应该执行提交传参过来的task,也就是新线程应该创建并执行附带过来的一个任务【敲黑板:不是从任务队列取,而是直接执行,所以任务会是FIFO的观点错误】
* execute提交任务时候 wc>=coresize时,并且任务taskqueue已经满了,maxsize>coresize的话,会按需尝试启动>coresize但是小于maxsize的线程数,并且执行execute提交过来的任务【敲黑板:不是从任务队列取,而是直接执行,所以任务会是FIFO的观点错误】
* 空闲的线程创建方式通常是新建线程替换corethread(重启那5个核心线程数),或者替换脏线程,但是这里用词是“usually”是通常情况,那么也就是说也有不是重启corethread和replace dying workers,我推测就是直接new出来的 worker了
*/
private boolean addWorker(Runnable firstTask, boolean core) {
......
w = new Worker(firstTask);
final Thread t = w.thread;
......
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
因为 t.start();执行了,所以会执行run()
/** Delegates main run loop to outer runWorker */
public void run() {
runWorker(this);
}
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {//如果task是null,则取队列去任务执行,反复执行,这里,gettask会在队列空闲时候并且长时间没有任务丢进池(可能直接丢给queue,可能直接以firsttask丢给新加的worker线程,所以这里不能笼统说是丢给queue)的时候,死循环干死多余的非coresize线程数
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(); //真正执行任务了,可能是execute(task)带过来的task,也可能是从queue里取的task
} catch (RuntimeException x) {
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; //有缺陷的完成任务=false,即无错误的完美完成了任务
} finally {
processWorkerExit(w, completedAbruptly);
}
}
4.所有worker线程执行完任务后会自己销毁吗? 我这里认为是,但是不知道是否是真的是?依据如下,上段代码里的processWorkerExit:
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;
workers.remove(w);
} finally {
mainLock.unlock();
}
tryTerminate();
int c = ctl.get();
if (runStateLessThan(c, STOP)) {
if (!completedAbruptly) { //优雅的完成了任务,前面又把他tryTerminate()了,所以这里要校验下最少可使用的线程数
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
if (min == 0 && ! workQueue.isEmpty()) //等于线程数可能为0是因为 允许核心线程数超时
min = 1;//队列也空了,核心线程数可能为0(允许超时),则还是要保证一个线程在那里运行的
if (workerCountOf(c) >= min) //当前线程数达标,不用新加线程了,那么直接return即可
return; // replacement not needed
}
addWorker(null, false); //如果线程不够了,那就加呗,前面够的情况,就已经return了,不会到这种不够的情况了,回答了前面池尽量保证运行线程数=coresize(5)的问题
}
}
5.execute(task)是借助在线程池初始化的时候的taskqueue来保证任务的先进先出先完成的吗?
答:不是,见问题3里的中文翻译,而且,即使假设task都是放到queue,FIFO,没有firsttask随新建线程执行的机制,一样也保证不了任务的是按顺序完成的,因为cpu时间分片,难说,谁先完成,但是基本保证任务按FIFO执行。
6.分析完这个wang作ba者dan的代码后,再问一个最基本的问题:假设没有任务提交的时候,线程池的线程数永远是coresize=5吗?
答:不是,这个让我想起薛定谔的猫:你打开看,猫就死了,或者猫活着,当然这里要表达的意思是:
如果你创建了线程池,但是一个任务都没有提交,其实看源码,他是一个线程都没有的,也就是coresize=0,只有你提交任务后,才会触发addworker添加线程的工作,触发执行gettask的死循环,触发校验最小线程数的逻辑
7.回到第一个问题,如果使用官方sdk代码来创建固定线程数是否更方便?
答:方便,但是不建议,特别是生产环境,还是建议自己使用ThreadPoolExecutor的构造函数并传入生产场景需要的线程数和指定size的有界队列arrayblokingqueue去构造固定线程数,以免造成内存溢出问题,或者其他user code问题,因为官方sdk使用的是无界队列linkedblockingqueue
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
同事harry的测试:
timeout参数也是无效,maxsize也是无效,这个池永远只有coresize个线程,直至宕机.
总结: 如下是浮云:
//java.util.concurrent.Executors java的Executors的工具类
//都是基于ThreadPoolExecutor 构建
Executors.newFixedThreadPool()
Executors.newCachedThreadPool()
Executors.newSingleThreadExecutor()
Executors.newScheduledThreadPool()
Executors.newSingleThreadScheduledExecutor()
这几个参见 Java并发编程之Executors与Thread Pool https://blog.csdn.net/kangkanglou/article/details/78974939
返璞归真是ThreadPoolExecutor,生产环境请别为了图方便而使用Executors的静态方法,还是按需配你自己需要的线程(池)吧!