这是我参与8月更文挑战的第25天,活动详情查看:8月更文挑战
WangScaler: 一个用心创作的作者。
声明:才疏学浅,如有错误,恳请指正。 上篇文章线程池的基本介绍,我们简单的介绍了线程池的执行过程,并提到了线程池的错误用法,这篇文章将告诉你,如何正确的创建线程池。
拒绝策略
当任务队列排满之后,如果正在运行的线程数小于最大线程数的时候,会创建新的线程执行任务,然而当我们的线程数达到最大线程数之后,就会采取拒绝策略,也就是线程池的最后一个参数handler。jdk的内置拒绝策略均实现了RejectedExecutionHandler接口,有以下几种:
-
AbortPolicy:抛出RejectedExecutionException异常,丢弃任务。也是默认的拒绝策略。
-
CallerRunsPolicy:既不丢弃也不抛出异常,而是将任务回退给调用的线程。
-
DiscardOldestPolicy:丢弃等待最久的任务,将当前任务加入任务队列。
-
DiscardPolicy:直接丢弃任务,也不抛出异常。
正确创建线程池
在上篇文章线程池的基本介绍,我已经说过简单使用的示例不可用于正式开发,如果你安装了阿里的代码检查软件,就能看到控制台提示的错误。
阿里巴巴的开发手册明确表示了线程资源只允许线程池提供,不允许在应用中自行显示创建。
为什么不能创建呢?
因为如果不自定义线程池,有可能造成系统创建大量的同类线程而导致内存被耗尽或者过度切换的问题。再一个就是像newFixedThreadPool
和newSingleThreadExecuto
r创建线程池的时候,任务队列使用的LinkedBlockingQueue
,我们在阻塞队列说过了。虽然LinkedBlockingQueue
是有界的,最大为Integer.MAX_VALUE
,所以说LinkedBlockingQueue
是无界队列也不过分,这么大的数值会将所有的任务加入任务队列。
面试常见的问题,就是实际开发中你使用哪个jdk中创建线程池的方法?
现在知道怎么回答了吧,那就是那个也没用,都是自己手写的。那么接下来就写一个例子。
package com.wangscaler.thread;
import java.util.concurrent.*;
/**
* @author WangScaler
* @date 2021/8/25 14:00
*/
public class ThreadPool {
public static void main(String[] args) {
ExecutorService pool = new ThreadPoolExecutor(2, 5, 1L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
try {
for (int i = 0; i < 9; i++) {
pool.execute(() -> System.out.println(Thread.currentThread().getName()));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
pool.shutdown();
}
}
}
线程数如何设置
可参考文章Java线程池如何合理配置核心线程数,不过部分内容有误。
-
cpu密集型:像加解密、压缩、计算等需要大量消耗cpu资源的任务,一般设置为cpu核数+1。
-
io密集型:通信,数据库交互、读写文件等等。
-
cpu核数*2。
-
CPU 核数 / (1 - 阻塞系数),阻塞系数一般认为在 0.8 ~ 0.9 之间。比如 8 核 CPU,按照公式就是 8 / ( 1 - 0.9 ) = 80 个线程数。
-
两个执行参数的区别
我们在多线程的三种写法中提到过,Runnable接口没有返回值,而Callable接口有返回结果。相对应的execute没有返回值,而submit有返回值。当你需要接受线程返回值的时候,应该使用submit。可参考文章# ExecutorService中submit和execute的区别
结语
上面提到了四种拒绝策略,应该根据你的业务场景做出选择。
来都来了,点个赞再走呗!
关注WangScaler,祝你升职、加薪、不提桶!