线程池:“线程池”,顾名思义就是一个线程缓存,线程是稀缺资源,如果被无限制的创建或者无意义的频繁,不仅会消耗系统资源,还会降低系统的稳定性,因此Java中提供线程池对线程进行统一分配、调优和监控。
如果并发的请求数量非常多,但每个线程执行的时间很短,这样就会频繁的创建和销毁线程,如此一来会大大降低系统的效率。可能出现服务器在为每个请求创建新线程和销毁线程上花费的时间和消耗的系统资源要比处理实际的用户请求的时间和资源更多。这就是为什么我们想要用线程池化管理的原因。最重要的原因是防止频繁的线程创建和销毁带来的系统的性能消耗。
线程池优势:
1、重用存在的线程,减少线程创建,消亡的开销,提高性能
2、提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
3、提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
Executor(执行)接口是线程池框架中最基础的部分,定义了一个用于执行Runnable的execute方法。
Executor下有一个重要子接口ExecutorService,其中定义了线程池的具体行为:
1:execute(Runnable command):执行Ruannable类型的任务,也就是父类中的方法,
2:submit(task):提交Callable或Runnable任务,并返回代表此任务的Future对象
3:shutdown():在完成已提交的任务后,不再接管新任务,
4:shutdownNow():停止所有正在履行的任务,不再处理任务。
5:isTerminated():测试是否所有任务都履行完毕了。
6:isShutdown():测试是否该ExecutorService已被关闭。
ExecutorService下的 AbstractExecutorService提供了一个可自定义返回对象的方法.不过我们接下来要说的重点是我们一般用的线程池类 ThreadPoolExecutor (是JDK的线程池类,不是spring封装的ThreadPoolTaskExecutor哦,不过ThreadPoolTaskExecutor也仅仅是提供了一些便捷定义的方法)
ThreadPoolExecutor:重点属性(其实只有一个属性ctl,其他的都是对这个属性的说明以及操作而已)
ctl:是对线程池的运行状态和线程池中有效线程的数量进行控制的一个字段, 它包含两部分的信息: 线程池的运行状态 (runState) 和线程池内有效线程的数量 (workerCount),这里可以看到,使用了Integer类型来保存,高3位保存runState,低29位保存workerCount。COUNT_BITS 就是29,CAPACITY就是1左移29位减1(29个1),这个常量表示workerCount的上限值,大约是5亿。凸(艹皿艹 ),应该不会有线程超过5亿的机器了吧。最下面的三个关于ctl的方法:不用说了,看英文字面意思也知道:获取运行状态,获取有效线程数,获取ctl。
线程池的5种状态
RUNNING = 1 << COUNT_BITS; //高3位为111,线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理。线程池的初始化状态是RUNNING。线程池被一旦被创建,就处
于RUNNING状态,并且线程池中的任务数为0!
SHUTDOWN = 0 << COUNT_BITS; //高3位为000,线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务(包括执行中和阻塞队列中的任务),调用线程池的shutdown()接时,线程池由RUNNING -> SHUTDOWN
STOP = 1 << COUNT_BITS; //高3位为001,线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。(已经在阻塞队列中等待的任务不会再处理,已经在处理的中断它,结束任务运行)。调用线程池的shutdownNow()接口时,线程池由(RUNNING or SHUTDOWN ) -> STOP。两种状态都可以直接进入到STOP状态
TIDYING = 2 << COUNT_BITS; //高3位为010,当所有的任务已终止,ctl记录的”工作线程数量”为0,线程池会变为TIDYING状态。并且执行回调的钩子函数terminated(),此方法。。可以复写干一些自己想干的事情。SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING。 当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING。
TERMINATED = 3 << COUNT_BITS; //高3位为011,线程池彻底终止,就变成TERMINATED状态。执行完钩子函数之后由TIDYING变成TERMINATED。
ThreadPoolExecutor有很多个构造函数,都是基于给一些默认值方便创建,这里讲一个最全的构造函数:
corePoolSize:的核心线程数,当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize;如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行;如果执行了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程。(由此可见,核心线程并不是线程池创建之后就立刻创建好的,也是用时才创建)
maximumPoolSize:线程池中允许的最大线程数。如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提当然是总线程数不会超过这个设置的最大线程数。(非核心线程是在核心线程已经全部创建,等待队列已满还有任务的情况下才会创建)
keepAliveTime:非核心线程被创建后,如果过了keepAliveTime设置的时间还没任务交给它跑,她就会自我销毁。
workQueue:用来保存等待被执行的任务的阻塞队列,且任务必须实现Runable接口(构造函数的泛型参数就是Runnable)。jdk提供了四种阻塞队列:
1、ArrayBlockingQueue:基于数组结构的有界阻塞队列,按FIFO排序任务;
2、LinkedBlockingQuene:基于链表结构的阻塞队列,按FIFO排序任务,吞吐量通常要高于ArrayBlockingQuene;
3、SynchronousQuene:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQuene;
4、priorityBlockingQuene:具有优先级的无界阻塞队列();
threadFactory:用来创建新线程工厂类,一般默认。默认使用Executors.defaultThreadFactory() 来创建线程。使用默认的ThreadFactory来创建线程时,会使新创建的线程具有相同的NORM_PRIORITY优先级并且是非守护线程,同时也设置了线程的名称。
handler:队列饱和策略。当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必
须采取一种策略处理该任务,线程池提供了4种策略:
1、AbortPolicy:直接抛出异常,默认策略;
2、CallerRunsPolicy:用调用者所在的线程来执行任务;
3、DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
4、DiscardPolicy:直接丢弃任务;
上面的4种策略都是ThreadPoolExecutor的内部类,都继承了RejectedExecutionHandler,所以自己可以继承RejectedExecutionHandler接口,自定义饱和策略,如记录日志或持久化存储
看下 线程池的执行流程图 :
1、提交任务到线程池。
2、创建核心线程处理任务。
3、提交速度过快,核心线程来不及处理,这put到阻塞队列中。
4、阻塞队列满了,创建非核心线程拉取任务处理。
5、线程达到最大线程数:抛弃策略处理。
一、提交任务到线程池 execute:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*下面的英文注释翻译一下。。。说的还挺清晰
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
int c = ctl.get();
//如果此时的工作线程数量小于核心线程
if (workerCountOf(c) < corePoolSize) {
//则新建一个线程放入线程池中; 并把任务添加到该线程中
//这里的第二个参数其实是后面要说的,true表示是根据核心线程大小判断创建,false是根据最大线程判断
if (addWorker(command, true))
return;
//没添加成功则重新获取ctl的值
c = ctl.get();
}
//线程池是running状态,并且加入到队列成功(注意,这里就说明核心线程满了之后,是先加入到了阻塞队列中,而不是直接创建非核心线程)
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
//如果此时线程池已经不再运行,则要移除这个command任务
if (! isRunning(recheck) && remove(command))
//移除后,执行拒绝策略
reject(command);
//如果此时工作线程是0.则创建worker
//第一个参数表示创建线程但是不去启动,(因为此时只是创建线程,并无任务,任务在阻塞队列中等待拉取。false表示是根据最大线程数量做出的限制)
//------!!!! 如果count>0.那啥也不用干,加入到队列就行了
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//如果加入到队列失败,这个时候证明队列满了嘛,就要创建非核心线程执行
//如果创建失败,证明已经达到最大的线程数量。。。。。接下来执行饱和策略
else if (!addWorker(command, false))
reject(command);
}
这里要注意一下addWorker(null, false);,也就是创建一个线程,但并没有传入任务,因为任务已经被添加到workQueue中了,所以worker在执行的时候,会直接从workQueue中获取任务。所以,在workerCountOf(recheck) == 0时执行addWorker(null, false);
二:创建线程addWorker:addWorker方法的主要工作是在线程池中创建一个新的线程并执行,firstTask参数 用于指定新增的线程执行的第一个任务,core参数为true表示在新增线程时会判断当前活动线程数是否少于corePoolSize,false表示新增线程前需要判断当前活动线程数是否少于maximumPoolSize
private boolean addWorker(Runnable firstTask, boolean core) {
//线程池在代码层面就是一个餐饮店。
//线程就是以 worker (工作者,工人,可以想象成每个线程就是一个送外卖的骑手)
//核心线程就是正式员工,非核心线程就是兼职的临时工人。
//而我们提交到线程池的每一个任务相当于一个送餐任务
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
//如果rs >= SHUTDOWN,则表示此时不再接收新任务. 下面三个参数判断是:
//1、rs == SHUTDOWN,这时表示关闭状态,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务。 今天要打烊了,只做已经接的单子,新单子不接了。
//所以这种状态下,firsTask必须为空,不为空的话证明是新单子,本店已经不接单了
//阻塞队列不为空,如果为空因为队列中已经没有任务了,不需要再添加线程了。 虽然已经打烊了,但是剩余单子没处理完,其他骑手都在派送,就需要让别的骑手送剩下的单子
if (rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty()))
return false;
for (;;) {
//获取线程数量
int wc = workerCountOf(c);
//当前线程池中的线程数量如果已经大于之前说的5亿上限或者说是大于了要比较的线程限制(核心线程或者是最大线程),骑手已经都在忙了,没法再安排空闲骑手了
if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// 尝试增加workerCount,如果成功,则跳出外循环. 成功联系上空闲的骑手
if (compareAndIncrementWorkerCount(c))
break retry;
//如果失败则重新获取线程池状态, 如果没联系到骑手,则在查一下有没有闲着的骑手
c = ctl.get(); // Re-read ctl
// 如果当前的运行状态不等于rs,说明状态已被改变,那就到外层循环继续执行。如果已经没有空闲骑手了,那就重复联系
//如果状态没变,还是自旋内循环逻辑去新增线程线程
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
//创建worker对象。并分配firstTask。
w = new Worker(firstTask);
//worker对象创建一个新的线程 (空闲骑手已经联系)
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get());
//再次判断状态
if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
//向workers中心添加此worker(在员工资料库里添加此骑手),workers就是一个hashset
workers.add(w);
int s = workers.size();
//记录最多创建过的线程数量。。。也就是鼎盛繁忙事情招聘过多少人
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
//新增线程后,启动线程(线程启动其实将会执行Worder类的run方法!!!)。这个时候新的骑手就处于工作状态啦
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
//出现异常,这里的逻辑其实就是清楚刚添加的worker,worker数量等
addWorkerFailed(w);
}
return workerStarted;
}
三、重点类 worker :创建Worker(Runnable firstTask) 方法。
Worker直接继承于AQS来实现独占锁的功能,而不是使用ReentrantLock来实现其到tryAcquire方法,它是不允许重入的,而ReentrantLock是允许重入的。这样的好处在于一个线程在获取锁之后证明其是在运行状态,不应该被中断。如果和ReentrantLock一样是可重入的,将会导致运行中的线程在执行其他获取锁的操作(如setCorePoolSize)时中断正在执行的线程。
tryAcquire:只有原子替换成功的才能获取到lock
runWorder方法:
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
//这一步很奇怪?没lock,怎么直接unlock了呢。这里其实里面是调用了重写的tryRelease方法。将state设置为正常的初始值0,这样就可以对其中断
//很明显,它不允许我们在worker没创建出来之前就中断它。就是你已经呼叫了骑手到店取餐,想中途无理由取消?肯定不行。等他到店告诉他订单被客户取消了更好,,这样也不怪你啊。
w.unlock(); // allow interrupts
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 {
//一系列的判断。这这里!!!!!就是worker线程执行你传进来的那个任务方法执行的地方
task.run();
} 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;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
//只是将状态直接设置为0
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
1. while循环不断地通过getTask()方法获取任务;
2. getTask()方法从阻塞队列中取任务;
3. 调用task.run()执行任务;
4. 如果task为null则跳出循环,执行processWorkerExit()方法;
5. runWorker方法执行完毕,也代表着Worker中的run方法执行完毕,销毁线程。
getTask()方法
private Runnable getTask() {
// timeOut变量表示上一次自旋从阻塞队列中取任务时是否超时,初始化false
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
//如果线程池状态rs >= SHUTDOWN,也就是非RUNNING状态,再进行以下判断:
//1. rs >= STOP,线程池是否正在stop;
//2. 阻塞队列是否为空。
//如果以上条件满足,则将workerCount减1并返回null。因为如果当前线程池状态的值是SHUTDOWN或以上时,不允许再向阻塞队列中添加任务。
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
//获取工作线程数
int wc = workerCountOf(c);
// timed是用于判断是否需要进行超时控制的。
// 默认核心线程创建后是永远不销毁的,也就是不超时。单然也可以设置
// wc > corePoolSize,表示当前线程池中的线程数量大于核心线程数量;
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
//减掉工作线程数量
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
// 如果有过期时间,keepAliveTime 传进去poll任务的时候,就会拉取不到方法,拉取不到返回null。timeOut返回为true
//继续自旋的时候 上面的 (timed && timedOut) 将变成true了。而此getTask方法将返回null。外面的runWorder方法里面将继续执行,销毁线程。这里是能 销毁线程的关键!!!!!!!
//这种设计。。。。只能卧槽。。。
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
runWorder执行出自旋逻辑后,将执行退出逻辑,也就是上面的非核心线程的过期 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();
//当线程池是RUNNING或SHUTDOWN状态时
if (runStateLessThan(c, STOP)) {
//如果worker是异常结束,那么会直接addWorker(不能全死掉线程,还有任务没结束)
if (!completedAbruptly) {
//除此以外还有两种情况要创建线程
//如果 允许核心线程超时。allowCoreThreadTimeOut=true,并且等待队列有任务,至少保留一个worker;
//如果 allowCoreThreadTimeOut=false,workerCount不少于corePoolSize
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
if (min == 0 && ! workQueue.isEmpty())
min = 1;
if (workerCountOf(c) >= min)
return; // replacement not needed
}
addWorker(null, false);
}
}
processWorkerExit执行完之后,工作线程被销毁,以上就是整个工作线程的生命周期。这里要注意的是,对于系统来讲,没有什么工作线程与核心线程的区别。它只是我们逻辑上对它的一区别。核心线程可以理解为线程池在阻塞队里未满时可以创建的最大的线程数量。所以,在销毁线程时,没有核心与非核心之分,只是对数量进行的控制。
阻塞队列:
ArrayBlockingQueue:生产者和消费者使用的是同一把锁,内部维护了一个数组。在生产和消费的时候,是直接将枚举对象插入或移除的,不会产生或销毁任何额外的对象实例。在创建时必须传入队列大小来构造队列。
LinkedBlockingQueue: 生产者的锁PutLock,消费者的锁takeLock。两把锁。链表接口,效率高于前者。在生产和消费的时候,需要创建Node对象进行插入或移除,大批量操作GC压力大(y因为其本身的链表结构是基于Node节点的。每一个元素都要 创建一个Node对象)。在创建时默认Integer最大值为队列最大容量,也可自己指定。
SynchronousQuene:一般用不到。但是在线程池中是一个特列。是一个无容器,以配对来进行读取的无界队列。好文:https://blog.csdn.net/yanyan19880509/article/details/52562039
DelayQueue:用于定时线程池,类似于树形结构,最小点堆的结构。父节点的执行时间小于子节点。但是!!!不保证同一层的父节点都小于下一层的子节点,不保证绝对的顺序,只保证最前节点最小。