java并发线程原理深入理解(一)-线程池及自定义线程池和延迟队列

119 阅读12分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第3天,点击查看活动详情

一、背景

随着业务的并发越来越高,就需要我们通过一些手段方式让我们的程序进行提速。本篇文章主要讲述通过并发线程的实现来提高程序响应速度,在提高速度的同时,也要理解为什么会提高

二、线程的使用

我们知道在程序使用过程中,多线程可以帮助我们程序提速,那是否是线程越多,速度也越快呢?下面通过两个简单的代码来看一下。

2-1、创建n个线程

image.png

通过上面代码可以看出,运行了10万次循环,同时开启10万次线程将一个随机数添加到结合中,需要时间为:36434毫秒,如果我们在程序中这么使用肯定是不行的,效率太低效了,那低效的原因是什么呢?

1、频繁的创建并销毁线程
2、cpu运行线程需要进行上下文切换(主因)

2-2、使用内置线程池

image.png

通过上面代码可以看出,同样运行了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实现的方法如下: image.png

newFixedThreadPool实现的方法如下: image.png

newSingleThreadExecutor实现的方法如下: image.png

可以看到三个线程池调用的方法都是ThreadPoolExecutor,只是传入的参数不同、最终导致运行结果有了不同的变化,可见ThreadPoolExecutor为线程池核心的处理方法,下面来对ThreadPoolExecutor探究一下

三、ThreadPoolExecutor方法参数探究

通过观察上面三个线程池,发现他们最终调用的都是ThreadPoolExecutor方法,不同的是三个线程池会传入不同的参数,这样运行的结果就不相同了,下面挨个先初探一下

image.png

第一个参数为:线程初始化的线程数
第二个参数为:最大的线程数,当前设置为Integer.MAX_VALUE(2的31次方-1如下:) image.png 第三个参数为:线程在不适用多长时间之后销毁
第四个参数为:销毁时间的单位
第五个参数为:任务进入的队列,对应的线程池,使用的队列如下

newCachedThreadPool:new SynchronousQueue<Runnable>()-同步队列
newFixedThreadPool:new LinkedBlockingQueue<Runnable>()-无界队列
newSingleThreadExecutor:new LinkedBlockingQueue<Runnable>()

3-1、newCachedThreadPool

image.png

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,看执行结果:

image.png 通过上图,可以发现,每个任务都是新创建了一个单独的线程进行执行的。

3-1-1-2、取消任务等待的3s

下面把任务run方法中的Thread.sleep注释,然后再次执行main方法,执行结果如下:

image.png 通过上图可以发现,线程1不再仅执行一个任务,而是执行了多个任务,可以得出结论:

  使用newCachedThreadPool的时候,当任务进来的时候,由于没有初始线程,这时候就会创建一个线程,进行任务的执行。

  当新的任务再次进来的时候,如果没有空闲线程、则再次进行创建,如果有空闲的线程则会使用空闲的线程进行任务的执行。

3-1-1-3、小结

image.png

线程池,用外包公司接任务干外包来举例,在newCashedThreadPool这个线程池中,根据传入的参数对着上图如下:

第一个初始化线程数-->核心员工
第二个扩展最大线程数-->临时非核心员工
过期限制先不进行说明
最后一个参数为-->任务队列

这样得到的外包公司,实际上没有自己的核心员工,有任务的时候就只能招聘临时短期合同的程序员。

当任务执行时间较长时线程的创建过程
  1. 首先老板先去接任务,老板人脉很好,接到了很多外包项目,但是总得有一个优先进行开发,因此旧先将其中一个人任务放到工作计划。

image.png

  1. 项目都接了,没有自己的核心员工,那就得新招聘一个员工(创建新的线程)

image.png 3) 将任务分配给临时招聘的员工进行开发,并且研发周期为一个月(程序中的3s)

image.png

  1. 客户催的不行,老板只能把任务再次提上日程,放入到队列中

image.png 5) 虽然任务提上日程,但是由于刚招聘的程序还有任务在研发,只能再次招聘新的程序员

image.png 6) 再次把任务给新招聘的worker2程序进行研发

image.png

通过上面的过程可以得知,由于前面的线程(程序员)都有事情在处理,并且由于招聘的临时程序几乎没有上限,这样每进来一个任务就会去创建(招聘)一个新的线程(程序员)来执行任务

当任务执行时间较短时线程的创建过程

1)、 前面由于程序员都有工作在研发,当一个月过后,那么招聘的第一个程序就空闲下来,然后回归到公司等待任务,如下:

image.png

2)、任务再次进来的时候再次放到任务队

image.png 3)、这时候老板发现worker1已经把第一个任务研发完成,并且没有到劳务合同的结束时间,在公司闲待着,那肯定不行,于是就把新任务再次给到了worker1

image.png

通过以上的过程,worker1就会在整个生命周期中执行多个任务,也就是经常提到的线程复用

3-2、newFixedThreadPool

image.png 通过上图可以发现,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,看执行结果:

image.png

通过上图可以发现,任务每10个走一次,然后再运行10个任务,并且看线程数没有超过10个。这就是因为在使用newFiexedThreadPool的时候传入的参数值为10,这样就只启动10个线程

3-2-1-2、取消任务等待的3s

image.png

通过上图可以发现,运行速度快了,实际上和任务等待3s执行的结果类似的,也是每10个跑一次任务,并且线程数都在10以内。

3-2-1-3、小结

newFiexedThreadPool通过传入的参数值,来创建对应数量的线程,并且线程数量不会增加,最大也是传入的参数值。

image.png

还以外包公司来举例,100个任务,传入的参数为10,也就是只有10个线程(员工),队列因为使用了LinkedBlockingQueue,因此可以认为是无限大的,可以进入队列看一下,如下:

image.png 这样老板接了100个任务,都会同时放入队列中,然后10个员工从队列拿走10个任务,然后队列中剩余90个,等10个员工的上批任务完成之后,再次动队列领取10个人,以此例推直到完成所有任务。

3-3、newSingleThreadExecutor

image.png 通过上图可以发现,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,看执行结果:

image.png

通过上图可以发现,任务每1个走一次,然后再运行1个任务,并且看线程数都是1。

3-3-1-2、取消任务等待的3s

image.png

通过上图可以看到,虽然线程数还是1,但是几乎一瞬间就完成了所有任务,这是因为执行任务的时候非常短,这样挨个执行任务的效率也会很高。

3-3-1-3、小结

newSingleThreadExecutor初始化设置的线程数和最大线程数都是1,这样实际上就只有一个线程可以执行任务。

image.png

还以外包公司举例,这个外包公司只有老板一个人,接了100个任务,都放在队列中,然后每次从队列中取一个任务进行执行,完成之后再次从队列中取一个任务,以此例推,直到完成所有任务。

image.png

四、项目中使用线程池

通过上面三个线程池的演示,已经很明确的了解到了这三个线程池的运行过程,但是实际上在项目中是不推荐使用这三个线程池的,在阿里的开发手册中也有说明。那为什么不推荐呢?这就是因为这三个线程池在大批量的高并发的情况下都会发生问题。

newCachedThreadPool:因为设置了最大线程数为Integer.MAX_VALUE这样就意味着线程数不受控制,如果大批量任务建立就会把CPU拉到100%,导致程序卡死。

newFixedThreadPoolnewSingleThreadExecutor虽然指定了固定的线程数,但是由于使用了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方法也可以看出,拒绝策略不仅仅是在饱和状态下使用,在线程池进入到关闭阶段同样需要使用到;

拒绝策略:

image.png ThreadPoolExecutor内部有实现4个拒绝策略:

(1)、CallerRunsPolicy,由调用execute方法提交任务的线程来执行这个任务;

(2)、AbortPolicy,抛出异常RejectedExecutionException拒绝提交任务;

(3)、DiscardPolicy,直接抛弃任务,不做任何处理;

(4)、DiscardOldestPolicy,去除任务队列中的第一个任务(最旧的),重新提交;

5-2-1-1、isRunning判断线程是否为运行状态

image.png

如下在核心线程数满了之后就会判断线程是否还在运行状态,因为只有在运行状态,后面的任务才可以继续提交。

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()方法大概流程图如下:

image.png

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();
        }
    }
}

七、自定义线程池队列拒绝策略

上面提到、当线程中队列无法再次添加任务的时候,就会抛出一个异常

image.png

在实际业务中,我们希望能自己实现这个异常,这样我们就可以在异常中写自己的想业务实现代码

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());

以上内如就是线程池相关的内容。