前言

暂无。

一、线程篇

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

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;}@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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.分布式调度框架

借助第三方框架:

  • 语雀:XXL-JOB
  • Quartz

定时调度相关的算法

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表达式。

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

  1. C#多线程之线程池篇2

    在上一篇C#多线程之线程池篇1中,我们主要学习了如何在线程池中调用委托以及如何在线程池中执行异步操作,在这篇中,我们将学习线程池和并行度.实现取消选项的相关知识. 三.线程池和并行度 在这一小节中,我 ...

  2. C#多线程之线程池篇1

    在C#多线程之线程池篇中,我们将学习多线程访问共享资源的一些通用的技术,我们将学习到以下知识点: 在线程池中调用委托 在线程池中执行异步操作 线程池和并行度 实现取消选项 使用等待句柄和超时 使用计时 ...

  3. 一篇就够,线程与线程池的那些事之线程池篇

    本文关键字: 线程 , 线程池 , 单线程 , 多线程 , 线程池的好处 , 线程回收 , 创建方式, 核心参数 , 底层机制 , 拒绝策略 , 参数设置 , 动态监控 , 线程隔离 线程和线程池相关 ...

  4. JUC源码分析-线程池篇(五):ForkJoinPool - 2

    通过上一篇(JUC源码分析-线程池篇(四):ForkJoinPool - 1)的讲解,相信同学们对 ForkJoinPool 已经有了一个大概的认识,本篇我们将通过分析源码的方式来深入了解 ForkJ ...

  5. 工具篇:Git与Github+GitLib常用操作(不定期持续更新)

    工具篇:Git与Github+GitLib常用操作(不定期持续更新) 前言: 写这个主要是打算自己用的,里边很多东西都是只要我自己看得懂,但是用了两个星期发现真是越用越简单,越用越好用,私以为得到了学 ...

  6. 《Autosar从入门到精通-实战篇》总目录_培训教程持续更新中...

    目录 一.Autosar入门篇: 1.1 DBC专题(共9篇) 1.2 ARXML专题(共35篇) 1.2.1 CAN Matrix Arxml(共28篇) 1.2.2 ASWC Arxml(共7篇) ...

  7. 智能家居项目开发: 设计模式(工厂模式)+ 线程池 + Socket (持续更新中)

    智能家居项目开发 一.智能家居功能细节拆分 控制区: 外设区: 面向对象类和对象的概念 结构体新玩法 二.工厂模式 1. 工厂模式的概念 2. 工厂模式的实现 3. 工厂模式使用及功能验证 三.智能家 ...

  8. java线程池游戏代码,Java游戏起步:(一)线程与线程池-JSP教程,Java技巧及代码...

    任何游戏都至少需要运行两个线程,主线程和gui线程 而线程池是一个管理运行线程的有用工具,下面的代码示范了一个线程池的实现方法~~ ********************************** ...

  9. 线程池的优点及其原理,代码实现线程池。简单、明了。

    一 使用线程池的好处 池化技术应用:线程池.数据库连接池.http连接池等等. 池化技术的思想主要是为了减少每次获取资源的消耗,提高对资源的利用率. 线程池提供了一种限制.管理资源的策略. 每个线程池 ...

最新文章

  1. maven项目的构建命令
  2. java mqtt paho_使用eclipse paho在java端实现MQTT消息的收发(客户端与服务端实例)...
  3. Android-使用AIDL进程间通信
  4. 单链表的快速排序(转)
  5. 路飞学城-python爬虫密训-第三章
  6. 大脑芯片首次进行人类测试 增强记忆指日可待?
  7. 【英语学习】【Daily English】U11 Work L03 He is a had guy to work for
  8. mysql rr gap nextkey_mysql中的各种锁把我搞糊涂啦~
  9. 【Linux】面试常问的 25+ 个 Linux 命令
  10. linux等候脚本,linux – 在bash脚本中继续之前等待通过ssh运行的脚本完成
  11. Jad批量反编译class
  12. 秒杀项目(2)集成redis
  13. mysql 加权_数据库 – MySQL中的加权平均计算?
  14. 【软件定义汽车】SOA框架介绍
  15. 站在智慧医院的制高点 阜外华中心血管病医院探索数字化融合实践
  16. Python复习的知识点
  17. 一个假冒的序列号被用来注册Internet Download Manager。IDM正在退出...解决办法
  18. 10GBASE-T SFP+电口模块
  19. java写文件用二进制分割_java分割二进制文件
  20. 输入两个整数,求;两者的和,差,积,商,余数。

热门文章

  1. PHP原生开发的各大音乐平台API接口
  2. 探究maven项目的打包方式
  3. 我们仍未知道那天所见的数据是怎么存放在内存中的
  4. 嵌入式开发,各类存储方式知多少?
  5. hive FULLJOIN中实现部分数据FULLJOIN另一部分数据LEFTJOIN的结果
  6. 薛定谔之猫_百度百科
  7. mongo 和shell交互方式
  8. 利用外观模式模拟股民炒股 C++
  9. fatal: 无法访问 ‘https://github.com/xxxx.git/‘:Could not resolve host: github.com
  10. 真正的Mybatis动态sql —MyBatis Dynamic SQL