对于线程池的使用一般要不然就是c+v技能,要不然就是直接使用别人初始化好的方法,至于具体要注意的细节一直没有在意过。前段时间看到了别人面试的题目,我发现我之前的理解和别人说的东西都是在满嘴跑火车!
本文风格交代: 代码demo+总结结论,所以可以直接拉下源代码观看博文提到的知识点会更加舒服:
1、线程池的创建以及常见参数的说明
package com.sakura.rain;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @ClassName Main
* @function [提交任务数量小于 核心+队列]
* @notice 持久化操作不做业务上处理
* @Author lcz
* @Date 2020/06/21 19:39
*/
public class Main00 {
public static void main(String[] args) {
/*
int corePoolSize: 核心线程数,开始创建的时候线程池中并没有任何线程,而是在任务到来的时候开始创建线程
也可以预创建线程:prestartAllCoreThreads(源码解释:覆盖默认当任务开始执行创建的方式,预先创建好核心线程数)
当线程池中的任务达到核心线程数的时候,后面的任务就会排队在任务中
如果超出了最大排队的数量,这个时候默认会拒绝后面的任务报错提示,这种称之为拒绝策略,拒绝策略就是这段代码需要演示的关键点
int maximumPoolSize, 最大支持创建的线程数量
long keepAliveTime, 线程保持活跃的时间,线程池中线程没有进行中的任务空闲过长时间会被回收
TimeUnit unit, 设置活跃时间的单位
LinkedBlockingDeque<Runnable> workQueue, 一个基于链表结构的阻塞队列
ThreadFactory threadFactory, 线程工厂,可以设置创建出的线程名字更加具有辨识度和意义
RejectedExecutionHandler handler 线程满了之后的拒绝策略,常见的有四种-默认策略是:AbortPolicy
这里面既然牵涉到了拒绝策略,那么就要列举一下常见的拒绝策略了。经常用到的四种拒绝策略都是
*/
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 5, 3, TimeUnit.SECONDS, new LinkedBlockingDeque<>(10), new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread.currentThread().setName("demo--" + System.currentTimeMillis());
System.out.println(Thread.currentThread().getName());
return null;
}
}, new ThreadPoolExecutor.AbortPolicy());
threadPoolExecutor.shutdown();
/*
* 下面这段代码展示的是当排队中的任务数量超出核心线程数
* 核心线程数:2
* 最大支持创建线程的数量为: 5
* 保持活跃的时间 3s
* 队列使用的是阻塞链表 LinkedBlockingDeque
* */
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 5, 3, TimeUnit.SECONDS, new LinkedBlockingDeque<>(10), new ThreadPoolExecutor.AbortPolicy());
/*在没有调用预备创建线程核心数之前,打印线程池中的线程个数*/
System.out.println(threadPool.getQueue());
for (int index = 0; index < 10; index++) {
int finalIndex = index;
threadPool.submit(() -> {
Thread.currentThread().setName("线程编码为: " + finalIndex);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
});
}
System.out.println("线程池中的队列数量" + threadPool.getQueue().size());
System.out.println("线程池中活跃的线程数" + threadPool.getActiveCount());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println();
System.out.println("线程池中的队列数量" + threadPool.getQueue().size());
System.out.println("线程池中活跃的线程数" + threadPool.getActiveCount());
System.out.println("线是否处于活跃的状态" + threadPool.getActiveCount());
threadPool.shutdownNow();
/*
打印的结果为:
[]
线程池中的队列数量8
线程池中活跃的线程数2
线程编码为: 1
线程编码为: 0
线程编码为: 3
线程编码为: 2
线程编码为: 4
线程编码为: 5
线程编码为: 6
线程编码为: 7
线程编码为: 8
线程编码为: 9
线程池中的队列数量0
线程池中活跃的线程数0
线是否处于活跃的状态0
在代码中一共创建了 10 个线程,但是总共调用了两个线程去执行,为什么不是五个:
核心线程: 10 > 2
队列: 10 = 10
任务进来之后有两个线程放到核心线程中进行执行,那么队列中还有8个,默认排列到线程中继续等待,知道前面的线程执行结束之后在拿出队列中的线程进行执行
* */
}
}
2、当任务书超出设置池子大小会发生什么
package com.sakura.rain;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @ClassName 任务数量大于队列核心之和
* @function [提交任务数 > 核心+队列]
* @notice 默认拒绝策略
* @Author lcz
* @Date 2020/06/21 20:30
*/
public class Main01 {
public static void main(String[] args) {
/*
* 下面这段代码展示的是当排队中的任务数量超出核心线程数
* 核心线程数:2
* 最大支持创建线程的数量为: 5
* 保持活跃的时间 3s
* 队列使用的是阻塞链表 LinkedBlockingDeque
* */
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 5, 3, TimeUnit.SECONDS, new LinkedBlockingDeque<>(10), new ThreadPoolExecutor.AbortPolicy());
/*在没有调用预备创建线程核心数之前,打印线程池中的线程个数*/
System.out.println(threadPool.getQueue());
for (int index = 0; index < 15; index++) {
int finalIndex = index;
threadPool.submit(() -> {
Thread.currentThread().setName("线程编码为: " + finalIndex);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
});
System.out.println("当任务提交index坐标为" + index + "时,活跃的线程数为:" + threadPool.getActiveCount());
}
System.out.println("线程池中的队列数量" + threadPool.getQueue().size());
System.out.println("线程池中活跃的线程数" + threadPool.getActiveCount());
/*为了窥探线程数量减少的过程,执行这段循环代码*/
int activeCount = threadPool.getActiveCount();
long currentTimeMillis = System.currentTimeMillis();
while (threadPool.getActiveCount() != 0) {
if (activeCount != threadPool.getActiveCount()) {
long timeMillis = System.currentTimeMillis();
System.out.println("减少时间间隔: " + (timeMillis - currentTimeMillis));
activeCount = threadPool.getActiveCount();
System.out.println("线程活跃数减少: " + threadPool.getActiveCount());
}
}
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println();
System.out.println("线程池中的队列数量" + threadPool.getQueue().size());
System.out.println("线程池中活跃的线程数" + threadPool.getActiveCount());
System.out.println("线是否处于活跃的状态" + threadPool.getActiveCount());
threadPool.shutdownNow();
/*
[]
当任务提交index坐标为0时,活跃的线程数为:1
当任务提交index坐标为1时,活跃的线程数为:2
当任务提交index坐标为2时,活跃的线程数为:2
当任务提交index坐标为3时,活跃的线程数为:2
当任务提交index坐标为4时,活跃的线程数为:2
当任务提交index坐标为5时,活跃的线程数为:2
当任务提交index坐标为6时,活跃的线程数为:2
当任务提交index坐标为7时,活跃的线程数为:2
当任务提交index坐标为8时,活跃的线程数为:2
当任务提交index坐标为9时,活跃的线程数为:2
当任务提交index坐标为10时,活跃的线程数为:2
当任务提交index坐标为11时,活跃的线程数为:2
当任务提交index坐标为12时,活跃的线程数为:3
当任务提交index坐标为13时,活跃的线程数为:4
当任务提交index坐标为14时,活跃的线程数为:5
线程池中的队列数量10
线程池中活跃的线程数5
线程编码为: 0
线程编码为: 1
线程编码为: 12
线程编码为: 14
线程编码为: 13
线程编码为: 3
线程编码为: 2
线程编码为: 5
线程编码为: 6
线程编码为: 4
线程编码为: 8
线程编码为: 7
线程编码为: 11
线程编码为: 9
线程编码为: 10
线程池中的队列数量0
线程池中活跃的线程数0
线是否处于活跃的状态0
在代码中一共创建了 15 个线程,但是总共调用了5个线程去执行,为什么这个时候是五个:
最开始调用核心线程数去执行,2个,队列中的任务持续进行增长
创建12个任务时,在下一个任务进来是发现队列中承载的能力已经达到了最大的限度,于是便开始扩充可以执行任务的线程数
从上面的打印结果证明这个结论是再形象不过了
* */
}
}
3、引出拒绝策略这个问题
package com.sakura.rain;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @ClassName Main02
* @function [提交任务数量 > 最大线程数 + 排队队列]
* @notice 持久化操作不做业务上处理
* @Author lcz
* @Date 2020/06/21 20:47
*/
public class Main02 {
public static void main(String[] args) {
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 5, 3, TimeUnit.SECONDS, new LinkedBlockingDeque<>(10), new ThreadPoolExecutor.AbortPolicy());
/*在没有调用预备创建线程核心数之前,打印线程池中的线程个数*/
System.out.println(threadPool.getQueue());
for (int index = 0; index < 20; index++) {
int finalIndex = index;
threadPool.submit(() -> {
Thread.currentThread().setName("线程编码为: " + finalIndex);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
});
}
System.out.println("线程池中的队列数量" + threadPool.getQueue().size());
System.out.println("线程池中活跃的线程数" + threadPool.getActiveCount());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println();
System.out.println("线程池中的队列数量" + threadPool.getQueue().size());
System.out.println("线程池中活跃的线程数" + threadPool.getActiveCount());
System.out.println("线是否处于活跃的状态" + threadPool.getActiveCount());
threadPool.shutdownNow();
/*
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@7a5d012c rejected from java.util.concurrent.ThreadPoolExecutor@79698539[Running, pool size = 5, active threads = 5, queued tasks = 10, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:112)
at com.sakura.rain.Main02.main(Main02.java:23)
线程编码为: 14
线程编码为: 0
线程编码为: 13
线程编码为: 12
线程编码为: 1
线程编码为: 2
线程编码为: 4
线程编码为: 6
线程编码为: 3
线程编码为: 5
线程编码为: 7
线程编码为: 8
线程编码为: 9
线程编码为: 10
线程编码为: 11
上面的运行结果可以看出,已经提交到线程池中任务还在继续执行直到结束,但是后续超过15的线程全部执行失败了,并且在程序中抛出了异常
我们在最后还有几行打印的输出也没有执行,说明程序异常抛出之后影响到了后面的代码的执行。
这种用上了最大启用线程数加上队列总和都没有办法承载的出现的异常处理的方式叫做线程的拒绝策略,在ThreadPoolExecutor中有四个内部实现类:
AbortPolicy
DiscardPolicy
DiscardOldestPolicy
CallerRunsPolicy
接下来的MainDemo中演示这四种拒绝策略的区别
*/
}
}
4、默认拒绝策略 AbortPloicy
package com.sakura.rain;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @ClassName Main03
* @function [提交的任务数量大于最大线程数加上队列]
* @notice 持久化操作不做业务上处理
* @Author lcz
* @Date 2020/06/21 21:01
*/
public class Main03 {
public static void main(String[] args) {
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 2, 3, TimeUnit.SECONDS, new LinkedBlockingDeque<>(2), new ThreadPoolExecutor.AbortPolicy());
/*在没有调用预备创建线程核心数之前,打印线程池中的线程个数*/
System.out.println("调用创建核心线程初始化方法之前: " + threadPool.getActiveCount());
/*再调用预备创建核心线程数之后,打印线程池中线程的个数*/
threadPool.prestartAllCoreThreads();
System.out.println("调用创建核心线程初始化方法之后:" + threadPool.getActiveCount());
/*休眠时间大于设置的默认活跃时间*/
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("休眠时间大于默认的活跃时间之后:" + threadPool.getActiveCount());
for (int index = 0; index < 15; index++) {
int finalIndex = index;
threadPool.submit(() -> {
Thread.currentThread().setName("线程编码为: " + finalIndex);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
});
}
threadPool.shutdown();
/*
*
* 调用创建核心线程初始化方法之前: 0
调用创建核心线程初始化方法之后:1
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@6cd8737 rejected from java.util.concurrent.ThreadPoolExecutor@13969fbe[Running, pool size = 2, active threads = 2, queued tasks = 2, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:112)
at com.sakura.rain.Main03.main(Main03.java:26)
线程编码为: 0
线程编码为: 2
线程编码为: 1
线程编码为: 3
在进行对拒绝策略说明之前,这里先说明两个方法:prestartCoreThread() and prestartAllCoreThreads()
默认创建的线程池中是没有执行的线程的,调用这个方法之后,会创建出默认核心数量的活跃线程
代码在创建线程池的时候执行了线程池的拒绝策略:AbortPolicy()
这种策略就是在上述任务数大于最大线程+队列容量情况时,对后续提交的任务进行拒绝策略,但是这种失败的拒绝提示会抛出异常。
出现异常的目的是为了能够及时发现问题,或者是针对问题的任务进行特殊处理或者是操作。
拒绝策略是实现了接口:RejectedExecutionHandler
重写了其中的:rejectedExecution()方法
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
* */
}
}
5、拒绝策略之DiscardPolicy
package com.sakura.rain;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @ClassName Main04
* @function [业务功能]
* @notice 持久化操作不做业务上处理
* @Author lcz
* @Date 2020/06/21 21:17
*/
public class Main04 {
public static void main(String[] args) {
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 2, 3, TimeUnit.SECONDS, new LinkedBlockingDeque<>(2), new ThreadPoolExecutor.DiscardPolicy());
for (int index = 0; index < 15; index++) {
int finalIndex = index;
threadPool.submit(() -> {
Thread.currentThread().setName("线程编码为: " + finalIndex);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
});
}
threadPool.shutdown();
/*
*
* Connected to the target VM, address: '127.0.0.1:61902', transport: 'socket'
线程编码为: 0
线程编码为: 3
线程编码为: 2
线程编码为: 1
拒绝策略: DiscardPolicy()
超出能力范围内的加入失败不抛出异常,但是后续线程池中不会执行这些方法
重写方法中的处理逻辑为:
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
* */
}
}
6、拒绝策略之DiscardOldestPolicy
package com.sakura.rain;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @ClassName Main04
* @function [业务功能]
* @notice 持久化操作不做业务上处理
* @Author lcz
* @Date 2020/06/21 21:17
*/
public class Main05 {
public static void main(String[] args) {
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 2, 3, TimeUnit.SECONDS, new LinkedBlockingDeque<>(2), new ThreadPoolExecutor.DiscardOldestPolicy());
for (int index = 0; index < 15; index++) {
int finalIndex = index;
threadPool.submit(() -> {
Thread.currentThread().setName("线程编码为: " + finalIndex);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
});
}
threadPool.shutdown();
/*
拒绝策略: DiscardOldestPolicy()
超出能力范围内的加入失败不抛出异常,但是后续线程池中不会执行这些方法
重写方法中的处理逻辑为:
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
* */
}
}
7、拒绝策略之CallerRunsPolicy
package com.sakura.rain;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @ClassName Main06
* @function [业务功能]
* @notice 持久化操作不做业务上处理
* @Author lcz
* @Date 2020/06/21 21:27
*/
public class Main06 {
public static void main(String[] args) {
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 2, 3, TimeUnit.SECONDS, new LinkedBlockingDeque<>(2), new ThreadPoolExecutor.CallerRunsPolicy());
for (int index = 0; index < 15; index++) {
int finalIndex = index;
threadPool.submit(() -> {
Thread.currentThread().setName("线程编码为: " + finalIndex);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
});
}
threadPool.shutdown();
/*
* 代码执行的结果:
线程编码为: 0
线程编码为: 3
线程编码为: 4
线程编码为: 6
线程编码为: 2
线程编码为: 1
线程编码为: 8
线程编码为: 7
线程编码为: 5
线程编码为: 9
线程编码为: 11
线程编码为: 10
线程编码为: 13
线程编码为: 12
线程编码为: 14
*
文档中解释为:只要线程在则会等待直到所有的方法都执行
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
* */
/*因此我在下面这段代码中使用方法强制关闭,演示在线程池关闭之后,后续的方法不再执行*/
ThreadPoolExecutor threadPoolClose = new ThreadPoolExecutor(1, 2, 3, TimeUnit.SECONDS, new LinkedBlockingDeque<>(2), new ThreadPoolExecutor.CallerRunsPolicy());
for (int index = 0; index < 15; index++) {
int finalIndex = index;
threadPoolClose.submit(() -> {
Thread.currentThread().setName("线程编码为: " + finalIndex);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
});
if (index == 10) {
threadPoolClose.shutdownNow();
System.out.println("线程池现在的状态为:");
System.out.println(threadPoolClose.isShutdown());
}
}
/*
* 线程编码为: 4
线程编码为: 0
线程编码为: 3
线程编码为: 2
线程编码为: 5
线程编码为: 1
Disconnected from the target VM, address: '127.0.0.1:50705', transport: 'socket'
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.sakura.rain.Main06.lambda$main$0(Main06.java:70)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266)
at java.util.concurrent.FutureTask.run(FutureTask.java)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
线程编码为: 9
线程编码为: 6
线程编码为: 7
线程池现在的状态为:
true
线程编码为: 8
执行结果看得出这个玩意真的不再执行了
*/
}
}
花了好几个晚上整理了这些代码demo,代码中的言辞和观点都是个人理解,不足之处还望指正。