网站首页 > java教程 正文
各位志同道合的朋友们大家好,我是一个一直在一线互联网踩坑十余年的编码爱好者,现在将我们的各种经验以及架构实战分享出来,如果大家喜欢,就关注我,一起将技术学深学透,我会每一篇分享结束都会预告下一专题
线程池介绍
线程池(Thread Pool):把一个或多个线程通过统一的方式进行调度和重复使用的技术,避免了因为线程过多而带来使用上的开销。
为什么要使用线程池?
- 可重复使用已有线程,避免对象创建、消亡和过度切换的性能开销。
- 避免创建大量同类线程所导致的资源过度竞争和内存溢出的问题。
- 支持更多功能,比如延迟任务线程池(newScheduledThreadPool)和缓存线程池(newCachedThreadPool)等。
线程池使用
创建线程池有两种方式:ThreadPoolExecutor 和 Executors,其中 Executors 又可以创建 6 种不同的线程池类型,会在下节讲,本节重点来看看 ThreadPoolExecutor 的使用。
ThreadPoolExecutor 的使用
线程池使用代码如下:
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 10, 10L, TimeUnit.SECONDS, new LinkedBlockingQueue(100));
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
// 执行线程池
System.out.println("Hello, Java.");
}
});
以上程序执行结果如下:
Hello, Java.
ThreadPoolExecutor 参数说明
ThreadPoolExecutor 构造方法有以下四个,如下图所示:
?
其中最后一个构造方法有 7 个构造参数,包含了前三个方法的构造参数,这 7 个参数名称如下所示:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
//...
}
其代表的含义如下:
① corePoolSize
线程池中的核心线程数,默认情况下核心线程一直存活在线程池中,如果将 ThreadPoolExecutor 的 allowCoreThreadTimeOut 属性设为 true,如果线程池一直闲置并超过了 keepAliveTime 所指定的时间,核心线程就会被终止。
② maximumPoolSize
线程池中最大线程数,如果活动的线程达到这个数值以后,后续的新任务将会被阻塞(放入任务队列)。
③ keepAliveTime
线程池的闲置超时时间,默认情况下对非核心线程生效,如果闲置时间超过这个时间,非核心线程就会被回收。如果 ThreadPoolExecutor 的 allowCoreThreadTimeOut 设为 true 的时候,核心线程如果超过闲置时长也会被回收。
④ unit
配合 keepAliveTime 使用,用来标识 keepAliveTime 的时间单位。
⑤ workQueue
线程池中的任务队列,使用 execute() 或 submit() 方法提交的任务都会存储在此队列中。
⑥ threadFactory
为线程池提供创建新线程的线程工厂。
⑦ rejectedExecutionHandler
线程池任务队列超过最大值之后的拒绝策略,RejectedExecutionHandler 是一个接口,里面只有一个 rejectedExecution 方法,可在此方法内添加任务超出最大值的事件处理。ThreadPoolExecutor 也提供了 4 种默认的拒绝策略:
- new ThreadPoolExecutor.DiscardPolicy():丢弃掉该任务,不进行处理
- new ThreadPoolExecutor.DiscardOldestPolicy():丢弃队列里最近的一个任务,并执行当前任务
- new ThreadPoolExecutor.AbortPolicy():直接抛出 RejectedExecutionException 异常
- new ThreadPoolExecutor.CallerRunsPolicy():既不抛弃任务也不抛出异常,直接使用主线程来执行此任务
包含所有参数的 ThreadPoolExecutor 使用代码:
public class ThreadPoolExecutorTest {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 1,
10L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(2),
new MyThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
threadPool.allowCoreThreadTimeOut(true);
for (int i = 0; i < 10; i++) {
threadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
}
class MyThreadFactory implements ThreadFactory {
private AtomicInteger count = new AtomicInteger(0);
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
String threadName = "MyThread" + count.addAndGet(1);
t.setName(threadName);
return t;
}
}
线程池执行方法 execute() VS submit()
execute() 和 submit() 都是用来执行线程池的,区别在于 submit() 方法可以接收线程池执行的返回值。
下面分别来看两个方法的具体使用和区别:
// 创建线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 10, 10L, TimeUnit.SECONDS, new LinkedBlockingQueue(100));
// execute 使用
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println("Hello, Java.");
}
});
// submit 使用
Future<String> future = threadPoolExecutor.submit(new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println("Hello, 老王.");
return "Success";
}
});
System.out.println(future.get());
以上程序执行结果如下:
Hello, Java.
Hello, 老王.
Success
线程池关闭
线程池关闭,可以使用 shutdown() 或 shutdownNow() 方法,它们的区别是:
- shutdown():不会立即终止线程池,而是要等所有任务队列中的任务都执行完后才会终止。执行完 shutdown 方法之后,线程池就不会再接受新任务了。
- shutdownNow():执行该方法,线程池的状态立刻变成 STOP 状态,并试图停止所有正在执行的线程,不再处理还在池队列中等待的任务,执行此方法会返回未执行的任务。
下面用代码来模拟 shutdown() 之后,给线程池添加任务,代码如下:
threadPoolExecutor.execute(() -> {
for (int i = 0; i < 2; i++) {
System.out.println("I'm " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
}
});
threadPoolExecutor.shutdown();
threadPoolExecutor.execute(() -> {
System.out.println("I'm Java.");
});
以上程序执行结果如下:
I'm 0
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.interview.chapter5.Section2$Lambda$2/1828972342@568db2f2 rejected from java.util.concurrent.ThreadPoolExecutor@378bf509[Shutting down, pool size = 1, active threads = 1, queued tasks = 0, completed tasks = 0]
I'm 1
可以看出,shutdown() 之后就不会再接受新的任务了,不过之前的任务会被执行完成。
相关面试题
1.ThreadPoolExecutor 有哪些常用的方法?
答:常用方法如下所示:
- submit()/execute():执行线程池
- shutdown()/shutdownNow():终止线程池
- isShutdown():判断线程是否终止
- getActiveCount():正在运行的线程数
- getCorePoolSize():获取核心线程数
- getMaximumPoolSize():获取最大线程数
- getQueue():获取线程池中的任务队列
- allowCoreThreadTimeOut(boolean):设置空闲时是否回收核心线程
2.以下程序执行的结果是什么?
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 10, 10L, TimeUnit.SECONDS, new LinkedBlockingQueue());
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 2; i++) {
System.out.println("I:" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
threadPoolExecutor.shutdownNow();
System.out.println("Java");
答:程序执行的结果是:
I:0
Java
java.lang.InterruptedException: sleep interrupted(报错信息)
I:1
题目解析:因为程序中使用了 shutdownNow() 会导致程序执行一次之后报错,抛出 sleep interrupted 异常,又因为本身有 try/catch,所以程序会继续执行打印 I:1 。
3.在 ThreadPool 中 submit() 和 execute() 有什么区别?
答:submit() 和 execute() 都是用来执行线程池的,只不过使用 execute() 执行线程池不能有返回方法,而使用 submit() 可以使用 Future 接收线程池执行的返回值。
submit() 方法源码(JDK 8)如下:
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
execute() 源码(JDK 8)如下:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
//..... 其他
}
4.说一下 ThreadPoolExecutor 都需要哪些参数?
答:ThreadPoolExecutor 最多包含以下七个参数:
- corePoolSize:线程池中的核心线程数
- maximumPoolSize:线程池中最大线程数
- keepAliveTime:闲置超时时间
- unit:keepAliveTime 超时时间的单位(时/分/秒等)
- workQueue:线程池中的任务队列
- threadFactory:为线程池提供创建新线程的线程工厂
- rejectedExecutionHandler:线程池任务队列超过最大值之后的拒绝策略
更多详细介绍,请见正文。
5.在线程池中 shutdownNow() 和 shutdown() 有什么区别?
答:shutdownNow() 和 shutdown() 都是用来终止线程池的,它们的区别是,使用 shutdown() 程序不会报错,也不会立即终止线程,它会等待线程池中的缓存任务执行完之后再退出,执行了 shutdown() 之后就不能给线程池添加新任务了;shutdownNow() 会试图立马停止任务,如果线程池中还有缓存任务正在执行,则会抛出 java.lang.InterruptedException: sleep interrupted 异常。
6.说一说线程池的工作原理?
答:当线程池中有任务需要执行时,线程池会判断如果线程数量没有超过核心数量就会新建线程池进行任务执行,如果线程池中的线程数量已经超过核心线程数,这时候任务就会被放入任务队列中排队等待执行;如果任务队列超过最大队列数,并且线程池没有达到最大线程数,就会新建线程来执行任务;如果超过了最大线程数,就会执行拒绝执行策略。
7.以下线程名称被打印了几次?
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 1,
10L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(2),
new ThreadPoolExecutor.DiscardPolicy());
threadPool.allowCoreThreadTimeOut(true);
for (int i = 0; i < 10; i++) {
threadPool.execute(new Runnable() {
@Override
public void run() {
// 打印线程名称
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
答:线程名被打印了 3 次。题目解析:线程池第 1 次执行任务时,会新创建任务并执行;第 2 次执行任务时,因为没有空闲线程所以会把任务放入队列;第 3 次同样把任务放入队列,因为队列最多可以放两条数据,所以第 4 次之后的执行都会被舍弃(没有定义拒绝策略),于是就打印了 3 次线程名称。
总结
ThreadPoolExecutor 是创建线程池最传统和最推荐使用的方式,创建时要设置线程池的核心线程数和最大线程数还有任务队列集合,如果任务量大于队列的最大长度,线程池会先判断当前线程数量是否已经到达最大线程数,如果没有达到最大线程数就新建线程来执行任务,如果已经达到最大线程数,就会执行拒绝策略(拒绝策略可自行定义)。线程池可通过 submit() 来调用执行,从而获得线程执行的结果,也可以通过 shutdown() 来终止线程池。
下一篇:Java 线程池
在公号 架构师修炼菜单中可自行获取专属架构视频资料,包括不限于 java架构、python系列、人工智能系列、架构系列,以及最新面试、小程序、大前端均无私奉献,你会感谢我的哈
?
往期精选(wx公号:架构师修炼)
分布式数据之缓存技术,一起来揭开其神秘面纱
分布式数据复制技术,今天就教你真正分身术
数据分布方式之哈希与一致性哈希,我就是个神算子
分布式存储系统三要素,掌握这些就离成功不远了
想要设计一个好的分布式系统,必须搞定这个理论
分布式通信技术之发布订阅,干货满满
分布式通信技术之远程调用:RPC
消息队列Broker主从架构详细设计方案,这一篇就搞定主从架构
消息中间件路由中心你会设计吗,不会就来学学
消息队列消息延迟解决方案,跟着做就行了
秒杀系统每秒上万次下单请求,我们该怎么去设计
【分布式技术】分布式系统调度架构之单体调度,非掌握不可
CDN加速技术,作为开发的我们真的不需要懂吗?
烦人的缓存穿透问题,今天教就你如何去解决
分布式缓存高可用方案,我们都是这么干的
每天百万交易的支付系统,生产环境该怎么设置JVM堆内存大小
你的成神之路我已替你铺好,没铺你来捶我
- 上一篇: Java线程池详解(java线程池运行原理)
- 下一篇: Java之线程池(java线程池实战)
猜你喜欢
- 2024-09-19 Java线程池的四种用法与使用场景(java线程池的原理和实现)
- 2024-09-19 尚学堂百战程序员:Java中的线程池
- 2024-09-19 Java线程池深度揭秘(java线程池入门)
- 2024-09-19 Java线程池(java线程池参数详解)
- 2024-09-19 如何更好的使用JAVA线程池(java中线程池的使用)
- 2024-09-19 史上最详细、最系统、最全面Java线程池解析
- 2024-09-19 好文推荐:深入分析Java线程池的实现原理
- 2024-09-19 Java的线程池是怎么回事?来看看这篇文章吧
- 2024-09-19 Java之线程池(java线程池实战)
- 2024-09-19 Java Web应用调优线程池:没你想的那么复杂
你 发表评论:
欢迎- 最近发表
-
- class版本不兼容错误原因分析(class更新)
- 甲骨文Oracle公司为Java的最新LTS版本做出改进
- 「版本发布」Minecraft Java开发版 1.19.4-pre1 发布
- java svn版本管理工具(svn软件版本管理)
- 我的世界1.8.10钻石在第几层(我的世界1.7.2钻石在哪层)
- Java开发高手必备:在电脑上轻松切换多个JDK版本
- 2022 年 Java 开发报告:Java 8 八年不到,开发者都在用什么?
- 开发java项目,选择哪个版本的JDK比较合适?
- Java版本选型终极指南:8 vs 17 vs 21特性对决!大龄程序员踩坑总结
- POI Excel导入(poi excel导入附件)
- 标签列表
-
- java反编译工具 (77)
- java反射 (57)
- java接口 (61)
- java随机数 (63)
- java7下载 (59)
- java数据结构 (61)
- java 三目运算符 (65)
- java对象转map (63)
- Java继承 (69)
- java字符串替换 (60)
- 快速排序java (59)
- java并发编程 (58)
- java api文档 (60)
- centos安装java (57)
- java调用webservice接口 (61)
- java深拷贝 (61)
- 工厂模式java (59)
- java代理模式 (59)
- java.lang (57)
- java连接mysql数据库 (67)
- java重载 (68)
- java 循环语句 (66)
- java反序列化 (58)
- java时间函数 (60)
- java是值传递还是引用传递 (62)
本文暂时没有评论,来添加一个吧(●'◡'●)