2、线程池篇 - 从理论基础到具体代码示例讲解(持续更新中......)

2023-11-17

前言

暂无。

一、线程篇

有关线程部分的知识整理请看我下面这篇博客:

1、线程篇 - 从理论到具体代码案例最全线程知识点梳理(持续更新中…)

二、线程池基础知识

线程池优点

他的主要特点为:

  1. 线程复用
  2. 管理线程,不需要频繁的创建和销毁线程
  3. 控制线程数量:处理过程中将任务放入队列,然后在线程创建后
    启动这些任务,如果线程数量超过了最大数量超出数量的线程排队等候,等其它线程执行完毕,
    再从队列中取出任务来执行。

线程池种类

  1. newFixedThreadPool - 固定线程数量
  2. newSingleThreadPool - 只有一个线程
  3. newCachedThreadPool - 可缓存的线程
  4. ScheduleThreadPoolExecutor - 定时线程

常见的3种阻塞队列

● ArrayBlockingQueue 由数组支持的有界队列
● LinkedBlockingQueue 由链接节点支持的可选有界队列
○ newFixedThreadPool
○ newSingleThreadPool
● DelayQueue 由优先级堆支持的、基于时间的调度无界队列(PriorityBlockingQueue )
○ ScheduleThreadPoolExecutor

他们的顶级接口都是:BlockingQueue。

线程池存在5种状态(生命周期)

和线程一样,线程池也存在生命周期。
底层源码,都是对线程池的状态进行判断,所以,此处把线程池的状态必须记住。

RUNNING    = -1 << COUNT_BITS; //高3位为111
SHUTDOWN   =  0 << COUNT_BITS; //高3位为000
STOP       =  1 << COUNT_BITS; //高3位为001
TIDYING    =  2 << COUNT_BITS; //高3位为010
TERMINATED =  3 << COUNT_BITS; //高3位为011

1、RUNNING

(1) 状态说明:线程池处在RUNNING状态时,能够接收新任务以及对已添加的任务进行处理
(2) 状态切换:线程池的初始化状态是RUNNING。换句话说,线程池被一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0!

2、 SHUTDOWN - 关门、停止

(1) 状态说明:线程池处在SHUTDOWN状态时,不接收新任务但能处理已添加的任务
(2) 状态切换:调用线程池的shutdown()接口时,线程池由RUNNING -> SHUTDOWN。

3、STOP

(1) 状态说明:线程池处在STOP状态时,不接收新任务不处理已添加的任务,并且会中断正在处理的任务
(2) 状态切换:调用线程池的shutdownNow()接口时,线程池由(RUNNING or SHUTDOWN ) -> STOP。

4、TIDYING(tidying) - 整理、收拾

(1) 状态说明:当所有的任务已终止,ctl记录的“任务数量”为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。
(2) 状态切换:当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING。 当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING。

5、 TERMINATED(terminated)

(1) 状态说明:线程池彻底终止,就变成TERMINATED状态。
(2) 状态切换:线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED。
进入TERMINATED的条件如下:
● 线程池不是RUNNING状态;
● 线程池状态不是TIDYING状态或TERMINATED状态;
● 如果线程池状态是SHUTDOWN并且workerQueue为空;
● workerCount为0;
● 设置TIDYING状态成功。

线程池状态转换

在这里插入图片描述
在这里插入图片描述

submit和exceute区别

在线程池的使用中,我们一般用ThreadPoolExecutor来创建线程池,创建好线程池后会将任务提交给线程池来执行。在提交任务的时候,JDK为我们提供了两种不同的提交方式,分别是submit()和excute()

1.接收参数不同
execute只能接受Runnable类型的任务
submit不管是Runnable还是Callable类型的任务都可以接受,但是Runnable返回值均为void,所以使用Future的get()获得的还是null。

2.对异常的处理不同
excute方法会抛出异常。
sumbit方法不会抛出异常。除非你调用Future.get()

3.对返回值的处理不同
execute方法不关心返回值。
submit方法有返回值,Future.

提交任务和执行任务:
线程池中,提交任务( submit() 和 execute())和执行任务的顺序是不一样的;
提交任务:核心线程 => 队列 => 非核心线程
执行任务:核心线程 => 非核心线程 => 队列

线程池有哪些参数

  1. keepalivetime:最大线程数到核心线程数之间的线程空闲了超过这个时间会慢慢销毁,再回到核心线程数量
  2. corePoolSize:核心线程数
  3. queueCapacity:任务队列容量(阻塞队列)
  4. maxPoolSize:最大线程数

线程池执行流程

  1. 当线程数小于核心线程数时,创建线程;
  2. 当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列;
  3. 当线程数大于等于核心线程数,且任务队列已满,线程数小于最大线程数,创建线程, 若线程数等于最大线程数,抛出异常,拒绝任务

四种拒绝策略

创建线程若线程数等于最大线程数,抛出异常,拒绝任务如下:

1、丢弃任务并抛出RejectedExecutionException异常(默认)
● new ThreadPoolExecutor.AbortPolicy();

2、丢弃任务但是不抛出异常 - Discard:
● new ThreadPoolExecutor.DiscardPolicy();

3、丢弃队列最前面的任务,最新任务入列
● new ThreadPoolExecutor.DiscardOldestPolicy();

4、由调用线程处理该任务
● new ThreadPoolExecutor.CallerRunsPolicy();

Executor 和Executors 区别

Java面试题之Executor 和Executors 区别

Executor 接口对象能执行我们的线程任务
Executors 工具类的不同方法按照我们的需求创建了不同的线程池,来满足业务的需求。
ExecutorService 接口继承了Executor接口并进行了扩展,提供了更多的方法,我们能够获得任务执行的状态并且可以获取任务的返回值。

为什么使用Executor框架创建线程比应用创建和管理线程更好?

A:其实这道题是在问你用线程池和不用线程池的区别。
对于Executor框架,他代表一系列线程池,如果有100个任务我们可以只创建几个线程去完成,节省cpu资源;
而对于Thread类,他就需要创建100个线程去执行这100个任务,消耗cpu资源。

三、线程池性能比较

通过一个案例分析各个线程池性能

此处,通过代码的方式,分析下面三个线程池在执行100个任务的时候,所耗时长是多少?并且分析为什么耗时不一样?

public class ThreadPoolDemo {
    public static void main(String[] args) {
        ExecutorService executorService1 = Executors.newFixedThreadPool(10); //慢
        ExecutorService executorService2 = Executors.newCachedThreadPool();  //快
        ExecutorService executorService3 = Executors.newSingleThreadExecutor(); //最慢
        //自定义的线程池--执行第(maximumpoolsize+capacity)个任务的时候报错(拒绝策略)
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor
                (10,20,0L, TimeUnit.MILLISECONDS,new ArrayBlockingQueue<>(20));
        for(int i = 0; i<100; i++){
            //executorService2.execute(new MyTask(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(1000L);
      }catch (Exception e){


      }

1.newCachedThreadPool

Q1:为什么newCachedThreadPool的方式更快?
那么我们就需要从底层源码的角度去分析。
A1:newCachedThreadPool
在这里插入图片描述

newCachedThreadPool的核心线程数是0,但是最大线程数是Integer的最大值。并且,没有用到队列存储任务
以我们上面代码为例,输出结果并没有体现出newCachedThreadPool这个线程池的线程复用的特点,因为我们故意让线程休眠了1s:如果有100个任务过来,他就会创建100个非核心线程;1000个任务过来,创建1000个非核心线程。。。来多少任务给你创建多少线程。

如果我们把休眠1s去掉,那么就体现了newCachedThreadPool线程复用的特点:100个任务,并不会给你创建100个线程,而是可能只有50个或者40个线程。因为同一个线程可能会执行多个任务。
那为什么加了1s休眠结果却是100个任务就会有100个线程被创建呢?
-答案也很简单,一个线程处理一个任务的速度很快的,可能1纳秒就处理完了,而你让他休眠了整整一秒。等他醒过来,任务早己被其他线程处理了。

面试回答的时候需要注意的点:
把你的场景给面试官描述清楚,比如休眠1s。不同的场景得到的结论是有差异的。 我们上面代码的结论就可以从输出结果中找到。
线程复用的确是newCachedThreadPool的特点,理论上来说线程复用就是一个线程处理了多个任务,但我上面休眠1s的场景却始终重现不了一个线程处理多个任务的输出结果,输出结果都是一个线程处理一个任务。

引发的思考:
并发编程中,理论上出现的问题在我们实际操作中很难让场景重现。所以我们就感觉他很难,包括在看一些书的时候,感觉都好抽象。 我们可以推测出某个场景,但是往往我们无法用代码把这个场景重现出来,以上面休眠1s为例。 休眠1s就是在假设这个任务执行的是sql查询操作,耗时为1s。这个场景还是好模拟的,不过还是有很多场景需要专门的测试人员使用工具去模拟。

2.newFixedThreadPool

A2: newFixedThreadPool
在这里插入图片描述

newFixedThreadPool的核心线程数是是10最大线程数也是10,因此他的线程池里面只有10个线程。
如果有100个任务过来,10个线程先来处理10个任务,剩下90个任务放到(无界)任务队列里面
10个任务执行完后,再从任务队列里面再拿10个任务,那么无界任务队列里面还剩下80个任务;

特点就是,10个任务10个任务的执行。你可以测试上面的代码块,每执行10个任务,都会有停顿。

3.newSingleThreadExecutor

A3:newSingleThreadExecutor
在这里插入图片描述

和newFixedThreadPool很像,唯一的区别就是只有1个核心线程,所以慢。

项目中使用哪种线程池

Q2:为什么阿里巴巴开发手册不推荐以上三种方式创建线程?
A:都有可能触发OOM内存溢出。
newFixedThreadPool和newSingleThreadExecutor都有无界阻塞队列,理论上说任务挤压是可以无限存放的,那么肯定溢出。
newCachedThreadPool就是任务多的时候虽然不会往任务队列放(队列里面只有一个任务的位置),但是他会给你一直创建线程,非常消耗cup资源最终内存溢出。
当然,我们前面说了,场景很重要。因为阿里并发量很大,所以不采用以上三种方式,但是普通小公司没有那么大的并发量完全是可以使用的。

那我们应该使用哪种方式创建线程呢?
-阿里推荐我们使用自定义线程池。

A1:通过分析上面三个方式的源码知道,其实是和线程池参数有关:
在这里插入图片描述
在这里插入图片描述

线程池如何调优?

看业务:

  1. 如果是IO型的(比如读取文件,耗时比较长。io占用高,cpu空闲),2*cpu核数+1
  2. 如果是CPU型的(计算操作。cpu占用高),根据CPU核数,cpu核数+1
    要跑多少任务(比如100个任务)

有自己实现过线程池吗?你是怎么实现的,里面的参数你是怎么设置的。(阿里面试题)
https://blog.csdn.net/sakuragio/article/details/100666596

CPU密集型 vs IO密集型

我们可以把任务分为计算(CPU)密集型和IO密集型。
计算密集型(CPU)任务的特点是要进行大量的计算,消耗CPU资源,比如计算圆周率、从1加到一亿等等,全靠CPU的运算能力。

CPU密集型也叫计算密集型,指的是系统的硬盘、内存性能相对CPU要好很多,此时,系统运作大部分的状况是CPU Loading 100%,CPU要读/写I/O(硬盘/内存),I/O在很短的时间就可以完成,而CPU还有许多运算要处理,CPU Loading很高。

假如在单核CPU情况下,线程池有6个线程,但是由于是单核CPU,所以同一时间只能运行一个线程,考虑到线程之间还有上下文切换的时间消耗,还不如单个线程执行高效。
所以!!!单核CPU处理CPU密集型程序,就不要使用多线程了。
假如是6个核心的CPU,理论上运行速度可以提升6倍。每个线程都有 CPU 来运行,并不会发生等待 CPU 时间片的情况,也没有线程切换的开销。
所以!!!多核CPU处理CPU密集型程序才合适,而且中间可能没有线程的上下文切换(一个核心处理一个线程)。
简单的说,就是需要CPU疯狂的计算。

四、定时类线程池详解

ScheduledThreadPoolExecutor基础知识

特点

1、定时类线程池,或者叫做延迟类线程池。因为这个类带来两个功能:1.定时 2.延迟。
2、在延迟类线程池中,是没有【非核心线程数】的,只有【核心线程数】。

ScheduledExecutorService重要API

在这里插入图片描述

三个重要方法 :

它接收SchduledFutureTask类型的任务,是线程池调度任务的最小单位,有三种提交任务的方式:

  1. schedule
  2. scheduledAtFixedRate
  3. scheduledWithFixedDelay

底层原理

它采用DelayQueue存储等待的任务

  1. DelayQueue内部封装了一个PriorityQueue(优先队列,最小堆),它会根据time的先后时间排序,若time相同则根据sequenceNumber排序;
    ● 首先按照time排序,time小的排在前面,time大的排在后面;
    ● 如果time相同,按照sequenceNumber排序,sequenceNumber小的排在前面,sequenceNumber大的排在后面,换句话说,如果两个task的执行时间相同,优先执行先提交的task。
  2. DelayQueue也是一个【无界队列】;

结构图

在这里插入图片描述

三个重要API代码演示

schedule方法【延迟执行任务】案例

下面的案例演示的是:延迟执行。
真实项目中,具体延迟多长时间是需要预估的。怎么预估?任务执行前后输出两个时间,然后相减。

案例1:

场景:当springboot项目启动的时候,故意延迟5s,让一些监听器、拦截器等初始化完毕(bean注入到容器)。
应用:在实际项目开发的时候,根据自己的业务场景设置延迟几秒执行。
在这里插入图片描述

案例2:

场景:主线程扔给延迟类线程池执行一个任务,且主线程继续执行自己的逻辑(异步);当延迟类线程池执行任务完毕会返回一个值给主线程,主线程拿到这个值再去做其他的业务逻辑。

异步的阻塞处理:主线程提交了任务给延迟类线程池之后,不会等延迟类线程池执行完任务再执行自己的代码,主线程而是直接执行自己下面的代码(业务逻辑); 当延迟类线程池执行完任务之后,会返回一个值,这个值会被主线程调用的get捕获到。
在这里插入图片描述
在这里插入图片描述

scheduleAtFixedRate[周期执行任务]案例

场景1:

场景描述:项目启动成功1s后,执行任务;然后每次间隔2s执行任务。

 ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(1);

在这里插入图片描述

场景2-发生异常—重要:
 ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(1);
   scheduledThreadPoolExecutor.scheduleAtFixedRate(() -> {
            log.info("send heart beat");
            long starttime = System.currentTimeMillis(), nowtime = starttime;
            while ((nowtime - starttime) < 5000) {
                nowtime = System.currentTimeMillis();
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            log.info("task over....");
            throw new RuntimeException("unexpected error , stop working");
        }, 1000, 2000, TimeUnit.MILLISECONDS);

第14行抛出一个异常,会造成什么结果呢?
— 上一节我们讲过,如果线程池在执行任务的时候发生异常,那么正在执行的这个任务就会被丢弃掉执行该任务的线程死亡,但是线程池依然存在,当新任务来的时候线程池会创建**非核心线程**。虽然线程池存在,但是没有任务了(因为只调用一次scheduleAtFixedRate,只有一个任务,而且这个任务发生异常被丢弃了),所以程序一直阻塞着等待任务。因此在实际开发中,我们必须在执行任务代码进行异常捕获(进行捕获后任务不会被抛弃)。

输出结果:
在这里插入图片描述

程序不会停止,一直处于运行状态,但是没有任务给他执行(由于异常没有被捕获,造成任务丢失,所以没有任务了) 。 
  因此在实际开发中,必须在执行任务的时候进行异常捕获(trycatch

调用两次scheduleAtFixedRate:

 ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(1);
      // 提交第一个任务。
      // 线程a,会发生异常,且这个异常没有被捕获。 
     //  那么,在发生异常的时候该【任务被丢弃】,该线程池执行该任务的【线程死掉】。
   scheduledThreadPoolExecutor.scheduleAtFixedRate(() -> {
                log.info("a线程:send heart beat");
                long starttime = System.currentTimeMillis(), nowtime = starttime;
                while ((nowtime - starttime) < 5000) {
                    nowtime = System.currentTimeMillis();
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.info("a线程:task over....");
                throw new RuntimeException("unexpected error , stop working");
        }, 1000, 2000, TimeUnit.MILLISECONDS);

        // 提交第二个任务。
       //    由于我们进行了异常捕获,该【线程不会死掉】且该【任务不会被丢弃】。
       //    注意:此时的线程是非核心线程,唯一的一个核心线程在上一次执行异常任务未捕获异常中死掉了。
        scheduledThreadPoolExecutor.scheduleAtFixedRate(() -> {
            try {
                log.info("b线程:send heart beat");
                long starttime = System.currentTimeMillis(), nowtime = starttime;
                while ((nowtime - starttime) < 5000) {
                    nowtime = System.currentTimeMillis();
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.info("b线程:task over....");
                throw new RuntimeException("unexpected error , stop working");
            }catch (Exception e){
                e.getMessage();
            }
        }, 1000, 2000, TimeUnit.MILLISECONDS);

执行结果:

D:\A-MyInstall\Java\JDK_64_1.8\bin\java.exe "-javaagent:D:\A-MyInstall\IntelliJ IDEA 2019.2.4\lib\idea_rt.jar=58193:D:\A-MyInstall\IntelliJ IDEA 2019.2.4\bin" -Dfile.encoding=UTF-8 -classpath D:\A-MyInstall\Java\JDK_64_1.8\jre\lib\charsets.jar;D:\A-MyInstall\Java\JDK_64_1.8\jre\lib\deploy.jar;D:\A-MyInstall\Java\JDK_64_1.8\jre\lib\ext\access-bridge-64.jar;D:\A-MyInstall\Java\JDK_64_1.8\jre\lib\ext\cldrdata.jar;D:\A-MyInstall\Java\JDK_64_1.8\jre\lib\ext\dnsns.jar;D:\A-MyInstall\Java\JDK_64_1.8\jre\lib\ext\jaccess.jar;D:\A-MyInstall\Java\JDK_64_1.8\jre\lib\ext\jfxrt.jar;D:\A-MyInstall\Java\JDK_64_1.8\jre\lib\ext\localedata.jar;D:\A-MyInstall\Java\JDK_64_1.8\jre\lib\ext\nashorn.jar;D:\A-MyInstall\Java\JDK_64_1.8\jre\lib\ext\sunec.jar;D:\A-MyInstall\Java\JDK_64_1.8\jre\lib\ext\sunjce_provider.jar;D:\A-MyInstall\Java\JDK_64_1.8\jre\lib\ext\sunmscapi.jar;D:\A-MyInstall\Java\JDK_64_1.8\jre\lib\ext\sunpkcs11.jar;D:\A-MyInstall\Java\JDK_64_1.8\jre\lib\ext\zipfs.jar;D:\A-MyInstall\Java\JDK_64_1.8\jre\lib\javaws.jar;D:\A-MyInstall\Java\JDK_64_1.8\jre\lib\jce.jar;D:\A-MyInstall\Java\JDK_64_1.8\jre\lib\jfr.jar;D:\A-MyInstall\Java\JDK_64_1.8\jre\lib\jfxswt.jar;D:\A-MyInstall\Java\JDK_64_1.8\jre\lib\jsse.jar;D:\A-MyInstall\Java\JDK_64_1.8\jre\lib\management-agent.jar;D:\A-MyInstall\Java\JDK_64_1.8\jre\lib\plugin.jar;D:\A-MyInstall\Java\JDK_64_1.8\jre\lib\resources.jar;D:\A-MyInstall\Java\JDK_64_1.8\jre\lib\rt.jar;D:\A-MyFile\学习文档\图灵学院\VIP第四期\二:并发编程专题\14.编发编程之Future&ForkJoin框架原理分析\tuling-juc-final\juc-threadpool\target\classes;D:\repository\junit\junit\4.12\junit-4.12.jar;D:\repository\org\hamcrest\hamcrest-core\1.3\hamcrest-core-1.3.jar;D:\repository\co\paralleluniverse\quasar-core\0.7.6\quasar-core-0.7.6.jar;D:\repository\org\hdrhistogram\HdrHistogram\2.1.9\HdrHistogram-2.1.9.jar;D:\repository\io\dropwizard\metrics\metrics-core\4.0.5\metrics-core-4.0.5.jar;D:\repository\org\latencyutils\LatencyUtils\2.0.3\LatencyUtils-2.0.3.jar;D:\repository\de\javakaffee\kryo-serializers\0.38\kryo-serializers-0.38.jar;D:\repository\com\google\protobuf\protobuf-java\2.6.1\protobuf-java-2.6.1.jar;D:\repository\com\google\guava\guava\19.0\guava-19.0.jar;D:\repository\com\esotericsoftware\kryo\4.0.0\kryo-4.0.0.jar;D:\repository\com\esotericsoftware\reflectasm\1.11.3\reflectasm-1.11.3.jar;D:\repository\com\esotericsoftware\minlog\1.3.0\minlog-1.3.0.jar;D:\repository\org\objenesis\objenesis\2.2\objenesis-2.2.jar;D:\repository\org\springframework\boot\spring-boot-starter-web\2.1.7.RELEASE\spring-boot-starter-web-2.1.7.RELEASE.jar;D:\repository\org\springframework\boot\spring-boot-starter\2.1.7.RELEASE\spring-boot-starter-2.1.7.RELEASE.jar;D:\repository\org\springframework\boot\spring-boot\2.1.7.RELEASE\spring-boot-2.1.7.RELEASE.jar;D:\repository\org\springframework\boot\spring-boot-autoconfigure\2.1.7.RELEASE\spring-boot-autoconfigure-2.1.7.RELEASE.jar;D:\repository\org\springframework\boot\spring-boot-starter-logging\2.1.7.RELEASE\spring-boot-starter-logging-2.1.7.RELEASE.jar;D:\repository\ch\qos\logback\logback-classic\1.2.3\logback-classic-1.2.3.jar;D:\repository\ch\qos\logback\logback-core\1.2.3\logback-core-1.2.3.jar;D:\repository\org\slf4j\slf4j-api\1.7.26\slf4j-api-1.7.26.jar;D:\repository\org\apache\logging\log4j\log4j-to-slf4j\2.11.2\log4j-to-slf4j-2.11.2.jar;D:\repository\org\apache\logging\log4j\log4j-api\2.11.2\log4j-api-2.11.2.jar;D:\repository\org\slf4j\jul-to-slf4j\1.7.26\jul-to-slf4j-1.7.26.jar;D:\repository\javax\annotation\javax.annotation-api\1.3.2\javax.annotation-api-1.3.2.jar;D:\repository\org\springframework\spring-core\5.1.9.RELEASE\spring-core-5.1.9.RELEASE.jar;D:\repository\org\springframework\spring-jcl\5.1.9.RELEASE\spring-jcl-5.1.9.RELEASE.jar;D:\repository\org\yaml\snakeyaml\1.23\snakeyaml-1.23.jar;D:\repository\org\springframework\boot\spring-boot-starter-json\2.1.7.RELEASE\spring-boot-starter-json-2.1.7.RELEASE.jar;D:\repository\com\fasterxml\jackson\core\jackson-databind\2.9.9\jackson-databind-2.9.9.jar;D:\repository\com\fasterxml\jackson\core\jackson-annotations\2.9.0\jackson-annotations-2.9.0.jar;D:\repository\com\fasterxml\jackson\core\jackson-core\2.9.9\jackson-core-2.9.9.jar;D:\repository\com\fasterxml\jackson\datatype\jackson-datatype-jdk8\2.9.9\jackson-datatype-jdk8-2.9.9.jar;D:\repository\com\fasterxml\jackson\datatype\jackson-datatype-jsr310\2.9.9\jackson-datatype-jsr310-2.9.9.jar;D:\repository\com\fasterxml\jackson\module\jackson-module-parameter-names\2.9.9\jackson-module-parameter-names-2.9.9.jar;D:\repository\org\springframework\boot\spring-boot-starter-tomcat\2.1.7.RELEASE\spring-boot-starter-tomcat-2.1.7.RELEASE.jar;D:\repository\org\apache\tomcat\embed\tomcat-embed-core\9.0.22\tomcat-embed-core-9.0.22.jar;D:\repository\org\apache\tomcat\embed\tomcat-embed-el\9.0.22\tomcat-embed-el-9.0.22.jar;D:\repository\org\apache\tomcat\embed\tomcat-embed-websocket\9.0.22\tomcat-embed-websocket-9.0.22.jar;D:\repository\org\hibernate\validator\hibernate-validator\6.0.17.Final\hibernate-validator-6.0.17.Final.jar;D:\repository\javax\validation\validation-api\2.0.1.Final\validation-api-2.0.1.Final.jar;D:\repository\org\jboss\logging\jboss-logging\3.3.2.Final\jboss-logging-3.3.2.Final.jar;D:\repository\com\fasterxml\classmate\1.4.0\classmate-1.4.0.jar;D:\repository\org\springframework\spring-web\5.1.9.RELEASE\spring-web-5.1.9.RELEASE.jar;D:\repository\org\springframework\spring-beans\5.1.9.RELEASE\spring-beans-5.1.9.RELEASE.jar;D:\repository\org\springframework\spring-webmvc\5.1.9.RELEASE\spring-webmvc-5.1.9.RELEASE.jar;D:\repository\org\springframework\spring-aop\5.1.9.RELEASE\spring-aop-5.1.9.RELEASE.jar;D:\repository\org\springframework\spring-context\5.1.9.RELEASE\spring-context-5.1.9.RELEASE.jar;D:\repository\org\springframework\spring-expression\5.1.9.RELEASE\spring-expression-5.1.9.RELEASE.jar;D:\repository\org\projectlombok\lombok\1.18.8\lombok-1.18.8.jar com.yg.edu.schedule.ScheduleThreadPoolRunner
20:46:41.249 [pool-1-thread-1] INFO com.yg.edu.schedule.ScheduleThreadPoolRunner - a线程:send heart beat
20:46:46.403 [pool-1-thread-1] INFO com.yg.edu.schedule.ScheduleThreadPoolRunner - a线程:task over....
20:46:46.403 [pool-1-thread-1] INFO com.yg.edu.schedule.ScheduleThreadPoolRunner - b线程:send heart beat
20:46:51.527 [pool-1-thread-1] INFO com.yg.edu.schedule.ScheduleThreadPoolRunner - b线程:task over....
20:46:51.527 [pool-1-thread-1] INFO com.yg.edu.schedule.ScheduleThreadPoolRunner - b线程:send heart beat
20:46:56.656 [pool-1-thread-1] INFO com.yg.edu.schedule.ScheduleThreadPoolRunner - b线程:task over....
20:46:56.656 [pool-1-thread-1] INFO com.yg.edu.schedule.ScheduleThreadPoolRunner - b线程:send heart beat
20:47:01.782 [pool-1-thread-1] INFO com.yg.edu.schedule.ScheduleThreadPoolRunner - b线程:task over....
20:47:01.782 [pool-1-thread-1] INFO com.yg.edu.schedule.ScheduleThreadPoolRunner - b线程:send heart beat
20:47:06.906 [pool-1-thread-1] INFO com.yg.edu.schedule.ScheduleThreadPoolRunner - b线程:task over....
20:47:06.906 [pool-1-thread-1] INFO com.yg.edu.schedule.ScheduleThreadPoolRunner - b线程:send heart beat
20:47:12.032 [pool-1-thread-1] INFO com.yg.edu.schedule.ScheduleThreadPoolRunner - b线程:task over....
20:47:12.032 [pool-1-thread-1] INFO com.yg.edu.schedule.ScheduleThreadPoolRunner - b线程:send heart beat
20:47:17.156 [pool-1-thread-1] INFO com.yg.edu.schedule.ScheduleThreadPoolRunner - b线程:task over....
20:47:17.156 [pool-1-thread-1] INFO com.yg.edu.schedule.ScheduleThreadPoolRunner - b线程:send heart beat
20:47:22.280 [pool-1-thread-1] INFO com.yg.edu.schedule.ScheduleThreadPoolRunner - b线程:task over....
20:47:22.280 [pool-1-thread-1] INFO com.yg.edu.schedule.ScheduleThreadPoolRunner - b线程:send heart beat

try,catch捕获之后的程序 – - 任务没有被丢弃,程序一直运行着该任务:
在这里插入图片描述

 ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(1);
场景3-任务堆积 —重要:

上面场景的任务只是简单的输出一行代码,真实场景中这个任务会进行非常复杂的操作,耗时10s,可是任务每2s就被执行,这会产生什么结果呢(注意:我只创建了一个核心线程执行任务,从始至终只有一个线程)?
--------会造成任务堆积。

下面,我们代码模拟一下上面的场景:

@Slf4j
public class ScheduleThreadPoolRunner {
    public static void main(String[] args) {
        // 创建延迟类线程池,核心线程数是1.
        ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(1);
        // 发心跳,service1->service2,每次过5s,发送一个心跳,证明s2可用
        // 注意:调用一次scheduleAtFixedRate是执行一个任务,调用两次是提交两个任务。
        scheduledThreadPoolExecutor.scheduleAtFixedRate(() -> {
            log.info("send heart beat");
            long starttime = System.currentTimeMillis(), nowtime = starttime;
            // 如果当前时间减去开始时间小于5s,则一直循环。
            while ((nowtime - starttime) < 5000) {
                nowtime = System.currentTimeMillis();
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            log.info("task over....");
            throw new RuntimeException("unexpected error , stop working");
        }, 1000, 2000, TimeUnit.MILLISECONDS);

结果:
在这里插入图片描述

代码不会报错。 虽然定义任务每2s执行一次,实际任务耗时5s;从结果来看,当任务执行结束,会立马再次执行任务。
造成的问题:我实际规定的是2s执行一次任务,你每次执行任务都耗费5s,随着时间增加,肯定会造成任务的堆积。以上场景等同于:a把家具从楼下搬到楼上耗时5s,b每2s都往楼下放新的家具;10s后,b放了5个家具,a搬上2个家具;那么100s后、1000s后、100000s后,任务不断堆积(往队列里面一直放),肯定就会造成OOM

注意:调用一次scheduleAtFixedRate提交一次任务,调用两次是提交两次任务。

那,怎么解决任务堆积问题呢?

  • 可能有人会想到上节课的内容:核心线程数创建完全,任务队列满,再来任务就开始创建非核心线程数;但是注意,我们的延迟类线程池没有非核心线程数;

而且,即使可以创建非核心线程数也不会让你一直创建下去。原因:一直创建最终肯定会造成OOM。

这时候就有人说,创建多点核心线程数不就行了;但是注意:调用一次scheduleAtFixedRate提交一次任务调用两次scheduleAtFixedRate是提交两次任务,每个任务都会对应一个核心线程数。我们上面的案例是创建一个核心线程,每2s执行一次任务都是这一个线程执行的;即使你创建10个核心线程,你只调用一次scheduleAtFixedRate,那么每2s执行一次任务都只是一个核心线程执行。
在这里插入图片描述
ScheduledThreadPoolExecutor给我们提供了另一个API,可以解决以上问题。
—scheduleWithFixedDelay方法。

scheduleWithFixedDelay

使用场景:分布式锁-redis,消息中间件(把任务放到消息队列,然后定时去消费任务)。

@Slf4j
public class ScheduleThreadPoolRunner {
    public static void main(String[] args) {
        ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(1);
        //发心跳,service1->service2,每次过5s,发送一个心跳,证明s2可用
        scheduledThreadPoolExecutor.scheduleWithFixedDelay(() -> {
            log.info("send heart beat");
            long starttime = System.currentTimeMillis(), nowtime = starttime;
            // 如果当前时间减去开始时间小于5s,则一直循环。
            while ((nowtime - starttime) < 5000) {
                nowtime = System.currentTimeMillis();
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            log.info("task over....");
            throw new RuntimeException("unexpected error , stop working");
        }, 1000, 2000, TimeUnit.MILLISECONDS);

输出结果:
在这里插入图片描述
只有当任务执行完毕后(花费5s),等2s后,再去执行这个任务;按照这个顺序依次执行的。

五、Java提供的三类定时策略以及调度算法

三类定时策略

1.定时类线程池ScheduledThreadPool

详细内容看上面笔记。

2.Java自带的定时器Timer

阿里规范不推荐使用。但是许多中间件中有用到Timer定时类。
Timer和ScheduledThreadPool的区别,经常在面试中被问到。

        Timer timer = new Timer();
        // 执行第一个任务,且在任务中发生异常
        timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                log.info("send heart beat");
                throw new RuntimeException("unexpected error , stop working");
            }
        },1000,2000);

        // 睡眠5s后,执行第二个任务
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 执行第二个任务
        timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                log.info("send heart beat");
                throw new RuntimeException("unexpected error , stop working");
            }
        },1000,2000);

执行结果:
在这里插入图片描述

当第一个任务发生异常的时候,该任务执行失败,Timer的线程死掉,第二个任务也不会被执行。
因为Timer是单线程的,线程都死掉了,第二个任务根本不会被执行:

 private final TimerThread thread = new TimerThread(queue);

  // 构造函数
  public Timer() {
        this("Timer-" + serialNumber());
    }

  public Timer(String name) {
        thread.setName(name);
        thread.start();
    }

总结-区别(面试题):
Timer是单线程的,在发生异常的时候,线程会死掉,即使第二个任务来了,也不会执行,可能直接导致程序挂掉。这也是为什么阿里规范不推荐使用Timer的原因,支持使用ScheduledThreadPool。 而ScheduledThreadPool创建一个核心线程,执行第一个任务异常时候,当第二个任务来的时候,线程池会再次创建线程(非核心线程)执行第二个任务。
在这里插入图片描述

3.分布式调度框架

借助第三方框架:

定时调度相关的算法

1、小顶堆算法

缺点:

  1. 每次顶部节点下沉的都要一步一步下沉,如果任务量很大的话非常浪费性能
  2. 按分钟、按小时、按天执行的任务都放到了堆中,存在无意义的比较:分钟的与小时的任务节点比较、分钟的与天的任务节点比较。

适应场景:数据量小的任务。

2、时间轮算法

链表或者 数组实现时间轮

在这里插入图片描述

年月日周期都在一个时间轮上。
描述:以2点为例,当时针走到2的时候(数组下标),会把2位置的链表中的所有元素取出来(任务)一起执行。这就不需要向小顶堆那样还需要比较了。
缺点:如果是13点执行任务怎么办?那么 就需要把12刻度改为24刻度。如果是13点30分执行任务怎么办?那就需要继续增加刻度,太麻烦复杂了。

round型时间轮

在这里插入图片描述

年月日周期都在一个时间轮上。
第一圈的时候round=1,第二圈的时候round=1-1,取出任务执行
很明显的一个缺点:这个任务被执行的前提是一遍又一遍的遍历所有任务,round-1,

分层时间轮

在这里插入图片描述

cron表达式使用的就是这个时间轮.
1-24时间轮表示一天的时间;天轮表示一天24小时
1-7时间轮表示一周七天;
1-30时间轮表示一个月;月轮表示20天
1-12时间轮表示一年;年轮表示12个月
如果是某个月的某号的某点执行某个任务,那么就需要年轮、月轮、天轮结合使用。这用的就是cron表达式。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

2、线程池篇 - 从理论基础到具体代码示例讲解(持续更新中......) 的相关文章

随机推荐

  • [1067]CDH6.3.2之Kerberos安全认证

    文章目录 Kerberos简介 Kerberos认证原理 Kerberos部署 Cloudera Manager平台上Kerberos的配置 在做此操作之前 请检查服务器时期是否正常 常用命令 登录Kerberos 创建Kerberos主体
  • 知识蒸馏论文翻译(9)—— Multi-level Knowledge Distillation via Knowledge Alignment and Correlation

    知识蒸馏论文翻译 9 Multi level Knowledge Distillation via Knowledge Alignment and Correlation 基于知识对齐和关联的多层次知识蒸馏 文章目录 知识蒸馏论文翻译 9
  • yum包管理器常见用法

    yum包管理器常见用法 yum源数据结构 yum源配置 yum config manager 快速添加 yum 源 自动寻找最快的yum源 仅支持Centos7 yum生成缓存 yum包管理器常用命令 RHEL8的yum yum软件仓库管理
  • 人脸识别从原理到实践

    目录 一 开箱即用 二 性能基线 三 训练 3 1 训练公开数据 3 2 训练自己的数据 四 视频教程 五 论文解读 引言 Loss 数据集 端上部署 人脸识别是目前深度学习领域应用最为广泛的领域之一 各大框架都有不错的开源项目 本文提供i
  • id注册

    直接在官网选择US就行了 然后就可以登陆
  • 剑指 Offer 07. 重建二叉树

    重建二叉树 思路 在前序遍历中找到根节点的值 然后在中序遍历中根据根节点的值划分左右子树 然后在左右子树里面递归调用同样的代码 再进行划分 package swordPointingToTheOffer import java util H
  • Centos设置nginx开机自启动

    第一步 进入到 lib systemd system 目录 root iz2z init d cd lib systemd system 第二步 创建nginx service文件 并编辑 vim nginx service 内如如下 Un
  • 抖音小程序实践四:实现小程序分享

    有时候我们要把一个小程序分享给别人 去看套餐 买东西之类的 是一个很常见的功能 但是在接入抖音小程序的时候 初始化右上角三个点并没有分享的入口 那看来不是要申请 就是有别的开发的口子了 下面我们一起了解下 从一个菜鸟的角度 我登录开发者后台
  • android 取消点击监听,Android中的活动中的软键盘打开和关闭监听...

    这仅适用于android 您的活动的windowSoftInputMode在清单中设置为adjustResize 您可以使用布局侦听器来查看键盘是否调整了活动的根布局 我为我的活动使用类似下面的基类 public class BaseAct
  • JetBrains :IDEA入门与使用技巧分享

    本文假设读者已掌握基础的开发方式 了解常见的概念 只是刚入手IDEA 不熟悉工具的使用方式而已 本文编写于 2019年7月27日 一 准备 以下是本文使用到的工具 工具与环境 IntelliJ IDEA Ultimate 2019 2 各个
  • DNS地址

    阿里云公共dns 223 5 5 5 223 6 6 6 腾讯公共dns 119 29 29 29 微步 拦截版 117 50 11 11 52 80 66 66 纯净版 117 50 10 10 52 80 52 52 360公共dns
  • 顺序表的定义及初始化代码实现(C语言)

    适合初学数据结构 不明白如何通过代码实现顺序表 超简洁代码如下 2020 10 16 第一次修改 顺序表结构定义的data是数组类型 应采用静态分配 模糊了静态分配与动态分配 已修改 错误程序L gt data 10 int malloc
  • 报错Description Resource Path Location Type Lifecycle mapping "org.eclipse.m2e.jdt.JarLifecycleMapping

    我这边是因为eclipse想做spring boot项目 因此想安装STS插件 结果装了几个版本 发现都没用 还导致POM XML文件报错 解决问题 1 卸载STS插件 help Install New Software 点击 what i
  • 一个矩阵乘以它本身的转置等于什么

    如果一个矩阵 A 乘以它本身的转置 AT 那么结果就是一个对角矩阵 对角线上的元素就是 A 矩阵中每一列的平方和 其余的元素都是 0 例如 如果 A 矩阵是 a11 a12 a21 a22 那么 A 乘以 AT 就是 a11 2 a21 2
  • 网道 JS教程 (第一天)

    地址 https wangdoc com javascript js 特点 单线程 事件驱动 非阻塞式设计 数据类型 数值 number 整数和小数 比如1和3 14 字符串 string 文本 比如Hello World 布尔值 bool
  • 关闭或者半关闭?!

    2017 05 20 LIBnids这个库 对于关闭的两个状态 理解的不是很清楚 就是 CLOSE算一个状态 CLOSE之前并不调用EXITING的语句 这就很尴尬 目前就当这两个是同一个状态 但是看着有些数据包是关闭的 结果显示不关闭 这
  • 模块学习笔记—(1)编码器减速电机

    模块学习笔记 1 编码器减速电机 编码器电机作用 编码器电机转动可以产生脉冲信号 根据脉冲信号 可以得出轮胎的转动速度 轮胎的位移 电机正反转等 电机介绍 我的编码器电机是130TT减速电机 电机轴转一圈可以产生13个脉冲信号输出 电机减速
  • 使用UUID获得一个不重复的16位账号的算法

    public static String getAccountIdByUUId int machineId 1 最大支持1 9个集群机器部署 int hashCodeV UUID randomUUID toString hashCode i
  • java运行环境

    计算机基础知识 二进制 如图 十进制和二进制的转换 字节 计算机中一个0或者一个1就是一个位 bit 不过并不是最小的数据单位 最下的数据单位是字节 Byte 一个字节等于8个位 计算机中任何数据的存储都是以字节的形式存储 1 B 8 bi
  • 2、线程池篇 - 从理论基础到具体代码示例讲解(持续更新中......)

    前言 暂无 一 线程篇 有关线程部分的知识整理请看我下面这篇博客 1 线程篇 从理论到具体代码案例最全线程知识点梳理 持续更新中 二 线程池基础知识 线程池优点 他的主要特点为 线程复用 管理线程 不需要频繁的创建和销毁线程 控制线程数量