持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第3天,点击查看活动详情
一、背景
随着业务的并发越来越高,就需要我们通过一些手段方式让我们的程序进行提速。本篇文章主要讲述通过并发线程的实现来提高程序响应速度,在提高速度的同时,也要理解为什么会提高
二、线程的使用
我们知道在程序使用过程中,多线程可以帮助我们程序提速,那是否是线程越多,速度也越快呢?下面通过两个简单的代码来看一下。
2-1、创建n个线程
通过上面代码可以看出,运行了10万次循环,同时开启10万次线程将一个随机数添加到结合中,需要时间为:36434毫秒,如果我们在程序中这么使用肯定是不行的,效率太低效了,那低效的原因是什么呢?
1、频繁的创建并销毁线程
2、cpu运行线程需要进行上下文切换(主因)
2-2、使用内置线程池
通过上面代码可以看出,同样运行了10万次循环,依然随机想集合中添加随机数,但是因为用了线程池,运行的效率提高了非常多,仅使用了119毫秒。
2-3、对比
通过上面两组代码,我们可以总结出以下异同:
1、创建的线程:第一段代码:创建了10w线程+1个主线程;第二段代码:1个线程池+1个主线程
2、创建的对象:在添加的时候都是创建了10万个随机数对象
那是不是就意味着用线程池就一定快呢?再看下面的例子
public class ThreadPoolDemo {
public static void main(String[] args) {
ExecutorService executorService1 = Executors.newCachedThreadPool();//快
ExecutorService executorService2 = Executors.newFixedThreadPool(10);//慢
ExecutorService executorService3 = Executors.newSingleThreadExecutor();//最慢
for (int i = 1; i <= 100; i++) {
executorService1.execute(new MyTask(i));
}
}
}
/***
* 项目
*/
class MyTask implements Runnable {
int i = 0;
public MyTask(int i) {
this.i = i;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "程序员做第" + i + "个项目");
try {
Thread.sleep(3000L);//业务逻辑
} catch (Exception e) {
e.printStackTrace();
}
}
}
以上代码首先创建了三个线程池,分别为:
newCachedThreadPool
newFixedThreadPool
newSingleThreadExecutor
然后分别使用这三个线程池执行下面的task方法,在task方法中让线程暂停3s来模拟查询数据库或者业务运行的时间,在通过运行的时候,运行的实际由快到慢顺序为:
newCachedThreadPool>newFixedThreadPool>newSingleThreadExecutor
这三个都是线程池,那为什么会有快慢之分呢?下面来逐个看下这三个线程池的实现方法
2-3-1、线程池的实现方法
newCachedThreadPool实现的方法如下:
newFixedThreadPool实现的方法如下:
newSingleThreadExecutor实现的方法如下:
可以看到三个线程池调用的方法都是ThreadPoolExecutor,只是传入的参数不同、最终导致运行结果有了不同的变化,可见ThreadPoolExecutor为线程池核心的处理方法,下面来对ThreadPoolExecutor探究一下
三、ThreadPoolExecutor方法参数探究
通过观察上面三个线程池,发现他们最终调用的都是ThreadPoolExecutor方法,不同的是三个线程池会传入不同的参数,这样运行的结果就不相同了,下面挨个先初探一下
第一个参数为:线程初始化的线程数
第二个参数为:最大的线程数,当前设置为Integer.MAX_VALUE(2的31次方-1如下:)
第三个参数为:线程在不适用多长时间之后销毁
第四个参数为:销毁时间的单位
第五个参数为:任务进入的队列,对应的线程池,使用的队列如下
newCachedThreadPool:new SynchronousQueue<Runnable>()-同步队列
newFixedThreadPool:new LinkedBlockingQueue<Runnable>()-无界队列
newSingleThreadExecutor:new LinkedBlockingQueue<Runnable>()
3-1、newCachedThreadPool
3-1-1、测试newCachedThreadPool的运行过程
3-1-1-1、让具体执行任务等待3s
public class ThreadPoolDemo {
public static void main(String[] args) {
ExecutorService executorService1 = Executors.newCachedThreadPool();//快
ExecutorService executorService2 = Executors.newFixedThreadPool(10);//慢
ExecutorService executorService3 = Executors.newSingleThreadExecutor();//最慢
for (int i = 1; i <= 100; i++) {
executorService1.execute(new MyTask(i));
}
}
}
/***
* 项目
*/
class MyTask implements Runnable {
int i = 0;
public MyTask(int i) {
this.i = i;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "程序员做第" + i + "个项目");
try {
Thread.sleep(3000L);//业务逻辑
} catch (Exception e) {
e.printStackTrace();
}
}
}
在上面代码中的run方法中,可以看到代码Thread.sleep(3000L)也就是当程序执行到这的时候会等待3s。这样在main方法中通过for循环执行的每个任务在运行run方法的时候都会等待3s,看执行结果:
通过上图,可以发现,每个任务都是新创建了一个单独的线程进行执行的。
3-1-1-2、取消任务等待的3s
下面把任务run方法中的Thread.sleep注释,然后再次执行main方法,执行结果如下:
通过上图可以发现,线程1不再仅执行一个任务,而是执行了多个任务,可以得出结论:
使用
newCachedThreadPool的时候,当任务进来的时候,由于没有初始线程,这时候就会创建一个线程,进行任务的执行。当新的任务再次进来的时候,如果没有空闲线程、则再次进行创建,如果有空闲的线程则会使用空闲的线程进行任务的执行。
3-1-1-3、小结
线程池,用外包公司接任务干外包来举例,在newCashedThreadPool这个线程池中,根据传入的参数对着上图如下:
第一个初始化线程数-->核心员工
第二个扩展最大线程数-->临时非核心员工
过期限制先不进行说明
最后一个参数为-->任务队列
这样得到的外包公司,实际上没有自己的核心员工,有任务的时候就只能招聘临时短期合同的程序员。
当任务执行时间较长时线程的创建过程
- 首先老板先去接任务,老板人脉很好,接到了很多外包项目,但是总得有一个优先进行开发,因此旧先将其中一个人任务放到工作计划。
- 项目都接了,没有自己的核心员工,那就得新招聘一个员工(创建新的线程)
3) 将任务分配给临时招聘的员工进行开发,并且研发周期为一个月(程序中的3s)
- 客户催的不行,老板只能把任务再次提上日程,放入到队列中
5) 虽然任务提上日程,但是由于刚招聘的程序还有任务在研发,只能再次招聘新的程序员
6) 再次把任务给新招聘的worker2程序进行研发
通过上面的过程可以得知,由于前面的线程(程序员)都有事情在处理,并且由于招聘的临时程序几乎没有上限,这样每进来一个任务就会去创建(招聘)一个新的线程(程序员)来执行任务
当任务执行时间较短时线程的创建过程
1)、 前面由于程序员都有工作在研发,当一个月过后,那么招聘的第一个程序就空闲下来,然后回归到公司等待任务,如下:
2)、任务再次进来的时候再次放到任务队
3)、这时候老板发现worker1已经把第一个任务研发完成,并且没有到劳务合同的结束时间,在公司闲待着,那肯定不行,于是就把新任务再次给到了worker1
通过以上的过程,worker1就会在整个生命周期中执行多个任务,也就是经常提到的线程复用
3-2、newFixedThreadPool
通过上图可以发现,newFixedThreadPool传入的参数,引用到了第一和第二个参数,这就意味着,初始化和最大的线程数量都是传入的nThreads。比如传入10,那么线程池就启动10个线程,并且最大也只有10个。
3-2-1、测试newFixedThreadPool查看运行过程
3-2-1-1、让任务运行时候等待3s
public class ThreadPoolDemo {
public static void main(String[] args) {
ExecutorService executorService1 = Executors.newCachedThreadPool();//快
ExecutorService executorService2 = Executors.newFixedThreadPool(10);//慢
ExecutorService executorService3 = Executors.newSingleThreadExecutor();//最慢
for (int i = 1; i <= 100; i++) {
executorService2.execute(new MyTask(i));
}
}
}
/***
* 项目
*/
class MyTask implements Runnable {
int i = 0;
public MyTask(int i) {
this.i = i;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "程序员做第" + i + "个项目");
try {
Thread.sleep(3000L);//业务逻辑
} catch (Exception e) {
e.printStackTrace();
}
}
}
在上面代码中的run方法中,可以看到代码Thread.sleep(3000L)也就是当程序执行到这的时候会等待3s。这样在main方法中通过for循环执行的每个任务在运行run方法的时候都会等待3s,看执行结果:
通过上图可以发现,任务每10个走一次,然后再运行10个任务,并且看线程数没有超过10个。这就是因为在使用newFiexedThreadPool的时候传入的参数值为10,这样就只启动10个线程
3-2-1-2、取消任务等待的3s
通过上图可以发现,运行速度快了,实际上和任务等待3s执行的结果类似的,也是每10个跑一次任务,并且线程数都在10以内。
3-2-1-3、小结
newFiexedThreadPool通过传入的参数值,来创建对应数量的线程,并且线程数量不会增加,最大也是传入的参数值。
还以外包公司来举例,100个任务,传入的参数为10,也就是只有10个线程(员工),队列因为使用了LinkedBlockingQueue,因此可以认为是无限大的,可以进入队列看一下,如下:
这样老板接了100个任务,都会同时放入队列中,然后10个员工从队列拿走10个任务,然后队列中剩余90个,等10个员工的上批任务完成之后,再次动队列领取10个人,以此例推直到完成所有任务。
3-3、newSingleThreadExecutor
通过上图可以发现,newSingleThreadExecutor,引用
ThreadPoolExecutor 第一和第二个参数都是1,这就意味着,初始化和最大的线程数量都是1。
3-3-1、测试newSingleThreadExecutor
3-3-1-1、让任务等待3s
public class ThreadPoolDemo {
public static void main(String[] args) {
ExecutorService executorService1 = Executors.newCachedThreadPool();//快
ExecutorService executorService2 = Executors.newFixedThreadPool(10);//慢
ExecutorService executorService3 = Executors.newSingleThreadExecutor();//最慢
for (int i = 1; i <= 100; i++) {
executorService3.execute(new MyTask(i));
}
}
}
/***
* 项目
*/
class MyTask implements Runnable {
int i = 0;
public MyTask(int i) {
this.i = i;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "程序员做第" + i + "个项目");
try {
Thread.sleep(3000L);//业务逻辑
} catch (Exception e) {
e.printStackTrace();
}
}
}
在上面代码中的run方法中,可以看到代码Thread.sleep(3000L)也就是当程序执行到这的时候会等待3s。这样在main方法中通过for循环执行的每个任务在运行run方法的时候都会等待3s,看执行结果:
通过上图可以发现,任务每1个走一次,然后再运行1个任务,并且看线程数都是1。
3-3-1-2、取消任务等待的3s
通过上图可以看到,虽然线程数还是1,但是几乎一瞬间就完成了所有任务,这是因为执行任务的时候非常短,这样挨个执行任务的效率也会很高。
3-3-1-3、小结
newSingleThreadExecutor初始化设置的线程数和最大线程数都是1,这样实际上就只有一个线程可以执行任务。
还以外包公司举例,这个外包公司只有老板一个人,接了100个任务,都放在队列中,然后每次从队列中取一个任务进行执行,完成之后再次从队列中取一个任务,以此例推,直到完成所有任务。
四、项目中使用线程池
通过上面三个线程池的演示,已经很明确的了解到了这三个线程池的运行过程,但是实际上在项目中是不推荐使用这三个线程池的,在阿里的开发手册中也有说明。那为什么不推荐呢?这就是因为这三个线程池在大批量的高并发的情况下都会发生问题。
newCachedThreadPool:因为设置了最大线程数为Integer.MAX_VALUE这样就意味着线程数不受控制,如果大批量任务建立就会把CPU拉到100%,导致程序卡死。
newFixedThreadPool和newSingleThreadExecutor虽然指定了固定的线程数,但是由于使用了LinkedBlockingQueue无界队列,如果大批量并发进来的时候,就会把所有任务都放到队列中,这样就会导致内存被拉大100%,导致程序卡死。
如果是传统企业,并发量不是很大,那么这三个线程池根据实际情况,也是可以选择使用的。
在阿里推荐大家使用自定义线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 20,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(10));
五、线程池的源码解析
5-1、使用自定义线程池
5-1-1、执行代码
使用自定义线程池,运行一下之前的代码,看下效果
public class ThreadPoolDemo {
public static void main(String[] args) {
ExecutorService executorService1 = Executors.newCachedThreadPool();//快
ExecutorService executorService2 = Executors.newFixedThreadPool(10);//慢
ExecutorService executorService3 = Executors.newSingleThreadExecutor();//最慢
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 20,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(10));
for (int i = 1; i <= 100; i++) {
threadPoolExecutor.execute(new MyTask(i));
}
}
}
/***
* 项目
*/
class MyTask implements Runnable {
int i = 0;
public MyTask(int i) {
this.i = i;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "程序员做第" + i + "个项目");
try {
Thread.sleep(3000L);//业务逻辑
} catch (Exception e) {
e.printStackTrace();
}
}
}
5-1-2、测试结果
pool-4-thread-2程序员做第2个项目
pool-4-thread-1程序员做第1个项目
pool-4-thread-5程序员做第5个项目
pool-4-thread-6程序员做第6个项目
pool-4-thread-4程序员做第4个项目
pool-4-thread-3程序员做第3个项目
pool-4-thread-7程序员做第7个项目
pool-4-thread-8程序员做第8个项目
pool-4-thread-9程序员做第9个项目
pool-4-thread-10程序员做第10个项目
pool-4-thread-13程序员做第23个项目
pool-4-thread-14程序员做第24个项目
pool-4-thread-17程序员做第27个项目
pool-4-thread-11程序员做第21个项目
pool-4-thread-18程序员做第28个项目
pool-4-thread-12程序员做第22个项目
pool-4-thread-15程序员做第25个项目
pool-4-thread-16程序员做第26个项目
pool-4-thread-19程序员做第29个项目
pool-4-thread-20程序员做第30个项目
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task thread.MyTask@5305068a rejected from java.util.concurrent.ThreadPoolExecutor@1f32e575[Running, pool size = 20, active threads = 20, queued tasks = 10, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
at thread.ThreadPoolDemo.main(ThreadPoolDemo.java:17)
pool-4-thread-1程序员做第11个项目
pool-4-thread-2程序员做第12个项目
pool-4-thread-6程序员做第13个项目
pool-4-thread-5程序员做第14个项目
pool-4-thread-4程序员做第15个项目
pool-4-thread-3程序员做第16个项目
pool-4-thread-7程序员做第17个项目
pool-4-thread-9程序员做第18个项目
pool-4-thread-8程序员做第19个项目
pool-4-thread-10程序员做第20个项目
通过上面代码以及测试结果,我们可以看到,执行了100个任务,自定义线程池设置初始化核心线程为10,最大线程为20,然后让无界队列设置了10。在此基础之上100个任务仅执行了30个,而且执行顺序为:1-10-->20-30-->10-20,顺序没有按照既定的顺序执行,那这是为什么呢?现就需要深入源码来看一下了。
5-2、线程池源码解析
5-2-1、execute()方法
不论使用java提供的线程池还是我们自定义线程池,最终都会执行execute()方法,这个方法就是线程池执行任务的第一步,下面进入这个方法,看下相关代码(对相关代码作用已添加注释)
public void execute(Runnable command) {
//首先判断任务是否为空,如果为空则抛出空指针
if (command == null)
throw new NullPointerException();
int c = ctl.get();
1、判断当前的线程数是否小于corePoolSize(设置的核心线程)如果是,
使用入参任务通过addWord(command, true)方法创建一个新的线程,第二个参数代表是否核心线程
如果能完成新线程创建exexute方法结束,成功提交任务;
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
2、在第一步没有完成任务提交;状态为运行并且能成功加入任务到工作队列后,
再进行一次check,如果状态在任务加入队列后变为了非运行(有可能是在执行到这里线程池shutdown了)
非运行状态下当然是需要reject;
然后再判断当前线程数是否为0(有可能这个时候线程数变为了0),如是,新增一个线程;
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
如果是创建一个null任务,任务在堵塞队列存在了就会从队列中取出 这样做的意义是
保证线程池在running状态必须有一个任务在执行
addWorker(null, false);
}
3、如果不能加入任务到工作队列,将尝试使用任务新增一个线程,如果失败,
则是线程池已经shutdown或者线程池已经达到饱和状态,所以reject;
从上面新增任务的execute方法也可以看出,拒绝策略不仅仅是在饱和状态下使用,
在线程池进入到关闭阶段同样需要使用到;
上面的几行代码还不能完全清楚这个新增任务的过程,
肯定还需要清楚addWorker方法才行:
else if (!addWorker(command, false))
reject(command);
}
实际上总结起来就是如下几步:
1、判断当前的线程数是否小于corePoolSize如果是,使用入参任务通过addWord方法创建一个新的线程,如果能完成新线程创建exexute方法结束,成功提交任务;
2、在第一步没有完成任务提交;状态为运行并且能成功加入任务到工作队列后,再进行一次check,如果状态在任务加入队列后变为了非运行(有可能是在执行到这里线程池shutdown了),非运行状态下当然是需要reject;然后再判断当前线程数是否为0(有可能这个时候线程数变为了0),如是,新增一个线程;
3、如果不能加入任务到工作队列,将尝试使用任务新增一个线程,如果失败,则是线程池已经shutdown或者线程池已经达到饱和状态,所以reject;
从上面新增任务的execute方法也可以看出,拒绝策略不仅仅是在饱和状态下使用,在线程池进入到关闭阶段同样需要使用到;
拒绝策略:
ThreadPoolExecutor内部有实现4个拒绝策略:
(1)、CallerRunsPolicy,由调用execute方法提交任务的线程来执行这个任务;
(2)、AbortPolicy,抛出异常RejectedExecutionException拒绝提交任务;
(3)、DiscardPolicy,直接抛弃任务,不做任何处理;
(4)、DiscardOldestPolicy,去除任务队列中的第一个任务(最旧的),重新提交;
5-2-1-1、isRunning判断线程是否为运行状态
如下在核心线程数满了之后就会判断线程是否还在运行状态,因为只有在运行状态,后面的任务才可以继续提交。
private static boolean isRunning(int c) {
return c < SHUTDOWN;
}
线程状态如下:
private static final int COUNT_BITS = Integer.SIZE - 3;
// 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;
其中COUNT_BITS是 int 位数
private static final int COUNT_BITS = Integer.SIZE - 3; //Integer.SIZE=32
所以实际 COUNT_BITS = 29,
用上面的5个常量表示线程池的状态,实际上是使用32位中的高3位表示;
通过上面的解析我们再去看刚刚测测试结果打印输出:1-10-->20-30-->10-20就明白其中原理了,用流程图可以再分析一下
excuete()方法大概流程图如下:
1、当1-10个任务进来的时候,首先进入10个核心线程数,进行执行;
2、当10-20个任务进来发现核心线程数已经使用完,并且是running状态,则放入队列中;
3、当20-30个任务进来的时候,发现核心线程数没有空闲状态,并且队列也没有空闲,这时候还有10个非核心线程数可用,因此20-30就放入到了非核心线程中执行
而线程执行的时候,先执行核心线程-->队列中任务-->非核心线程中任务。那为什么是这个顺序呢?这就设计到了addWorker这个方法,下面看下这个方法
5-2-2、addWorker()方法
private boolean addWorker(Runnable firstTask, boolean core) {
retry: goto写法 用于重试
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
线程状态非运行并且非shutdown状态任务为空,队列非空就不能新增线程了
return false;
for (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
当前现场大于等于最大值
等于核心线程数 非核心大于等于线程池数 说明达到了阈值
最大线程数 就不新增线程
return false;
if (compareAndIncrementWorkerCount(c)) ctl+1 工作线程池数量+1 如果成功
就跳出死循环。
cas操作 如果为true 新增成功 退出
break retry;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry; 进来的状态和此时的状态发生改变 重头开始 重试
// else CAS failed due to workerCount change; retry inner loop
}
}
上面主要是对ctl工作现场+1
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask); 内部类 封装了线程和任务 通过threadfactory创建线程
final Thread t = w.thread; 毎一个worker就是一个线程数
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());
小于shutdown就是running状态
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
SHUTDOWN 和firstTask 为空是从队列中处理任务 那就可以放到集合中
线程还没start 就是alive就直接异常
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s; 记录最大线程数
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start(); 启动线程
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);//失败回退 从wokers移除w 线程数减1 尝试结束线程池
}
return workerStarted;
}
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
/**
* This class will never be serialized, but we provide a
* serialVersionUID to suppress a javac warning.
*/
private static final long serialVersionUID = 6138294804551838833L;
/** Thread this worker is running in. Null if factory fails. */
正在运行woker线程
final Thread thread;
/** Initial task to run. Possibly null. */
传入的任务
Runnable firstTask;
/** Per-thread task counter */
完成的任务数 监控用
volatile long completedTasks;
/**
* Creates with given first task and thread from ThreadFactory.
* @param firstTask the first task (null if none)
*/
Worker(Runnable firstTask) {
禁止线程中断
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
/** Delegates main run loop to outer runWorker */
public void run() {
runWorker(this);
runWorker方法
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();//获取当前线程
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts 把state从-1改为0 意思是可以允许中断
boolean completedAbruptly = true;
try { task不为空 或者阻塞队列中拿到了任务
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
如果当前线程池状态等于stop 就中断
//Thread.interrupted() 中断标志
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) {
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++; 完成任务数+1
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
getTask方法
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);//获取线程池运行状态
shuitdown或者weikong 那就工作现场-1 同事返回为null
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
重新获取工作线程数
int wc = workerCountOf(c);
timed是标志超时销毁
allowCoreThreadTimeOut true 核心线程池也是可以销毁的
// 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 ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
六、ScheduledThreadPoolExecutor
6-1、三种延迟队列
schedule:延迟多长时间之后只执行一次;
scheduledAtFixedRate固定:延迟指定时间后执行一次,之后按照固定的时长周期执行;
scheduledWithFixedDelay非固定:延迟指定时间后执行一次,之后按照:上一次任务执行时长 + 周期的时长 的时间去周期执行;
6-2、演示代码
public class ScheduledThreadPoolExecutorExample {
public static void main(String[] args) {
ScheduledThreadPoolExecutor executor = (ScheduledThreadPoolExecutor) Executors.newScheduledThreadPool(5);
Task task = new Task("任务");
System.out.println("Created : " + task.getName());
// executor.schedule(task, 2, TimeUnit.SECONDS);
// executor.scheduleWithFixedDelay(task, 0, 2, TimeUnit.SECONDS); //任务+延迟
executor.scheduleAtFixedRate(task, 0, 1, TimeUnit.SECONDS);//任延迟取最大值 稳定定时器
}
}
class Task implements Runnable {
private String name;
public Task(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void run() {
System.out.println("Executing : " + name + ", Current Seconds : " + new Date().getSeconds());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
七、自定义线程池队列拒绝策略
上面提到、当线程中队列无法再次添加任务的时候,就会抛出一个异常
在实际业务中,我们希望能自己实现这个异常,这样我们就可以在异常中写自己的想业务实现代码
7-1、创建自己的异常实现类
public class MyRejectedExecutionHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("my exception");
}
}
7-2、在线程池中使用
在最后调用我们自己写的异常处理类
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 20,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(10),new MyRejectedExecutionHandler());
以上内如就是线程池相关的内容。