降低资源消耗
:通过池化技术重复利用已创建的线程,降低线程创建和销毁造成的损耗。提高响应速度
:任务到达时,无需等待线程创建即可立即执行。提高线程的可管理性
:线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。提供更多更强大的功能
:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行RUNNING
:线程池一旦被创建,就处于 RUNNING 状态,任务数为 0,能够接收新任务,对已排队的任务进行处理。SHUTDOWN
:不接收新任务,但能处理已排队的任务。调用线程池的 shutdown() 方法,线程池由 RUNNING 转变为 SHUTDOWN 状态。STOP
:不接收新任务,不处理已排队的任务,并且会中断正在处理的任务。调用线程池的 shutdownNow() 方法,线程池由(RUNNING 或 SHUTDOWN ) 转变为 STOP 状态。TIDYING
:
TERMINATED
:线程池彻底终止。线程池在 TIDYING 状态执行完 terminated() 方法就会由 TIDYING 转变为 TERMINATED 状态。newFixedThreadPool
newSingleThreadExecutor
newCachedThreadPool
newScheduledThreadPool
newSingleThreadScheduledExecutor
newWorkStealingPool
Executors工具类创建的线程池队列或线程默认为Integer.MAX_VALUE,容易堆积请求 阿里巴巴Java开发手册:
FixedThreadPool
和 SingleThreadPool
:允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOMCachedThreadPool
:允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM推荐使用ThreadPoolExecutor
类根据实际需要自定义创建
ThreadPoolExecutor类主要有以下七个参数:
int corePoolSize
: 核心线程池大小,线程池中常驻线程的最大数量int maximumPoolSize
: 最大核心线程池大小(包括核心线程和非核心线程)long keepAliveTime
: 线程空闲后的存活时间(仅适用于非核心线程)TimeUnit unit
: 超时单位BlockingQueue workQueue
: 阻塞队列ThreadFactory threadFactory
: 线程工厂:创建线程的,一般默认RejectedExecutionHandler handle
: 拒绝策略拒绝策略就是当队列满时,线程如何去处理新来的任务。
拒绝执行
的异常默认的策略
就是AbortPolicy
,由于ExecutorService接口的系列ThreadPoolExecutor都没有显示的设置拒绝策略,所以默认的都是这个。当前线程处理
。丢弃这个任务
,不触发任何动作把第一个人打死,然后自己代替
ArrayBlockingQueue
:使用数组实现的有界阻塞队列,特性先进先出LinkedBlockingQueue
:使用链表实现的阻塞队列,特性先进先出,可以设置其容量,默认为Interger.MAX_VALUEPriorityBlockingQueue
:使用平衡二叉树堆,实现的具有优先级的无界阻塞队列DelayQueue
:无界阻塞延迟队列,队列中每个元素均有过期时间,当从队列获取元素时,只有过期元素才会出队列。队列头元素是最块要过期的元素。SynchronousQueue
:一个不存储元素的阻塞队列,每个插入操作,必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态一个线程池 core 7; max 20 , queue: 50, 100并发进来怎么分配的?
答:先有7个能直接得到执行, 接下来把50个进入队列排队等候, 在多开13个继续执行。 现在 70 个被安排上了。 剩下 30 个默认执行饱和策略。
execute
提交没有返回值,不能判断是否执行成功。只能提交一个Runnable的对象submit
会返回一个Future
对象,通过Future的get()
方法来获取返回值,submit提交线程可以吃掉线程中产生的异常
,达到线程复用
。当get()执行结果时异常才会抛出。原因是通过submit提交的线程,当发生异常时,会将异常保存,待future.get()
时才会抛出。shutdown()
:不再继续接收新的任务,执行完成已有任务后关闭shutdownNow()
:直接关闭,若果有任务尝试停止业务异常
用try catch
处理,保证线程不会异常退出业务之外的异常
我们不可预见的,创建线程池设置ThreadFactory的UncaughtExceptionHandler
可以对未捕获的异常做保底处理,通过submit
提交任务,可以吃掉异常并复用线程;想要捕获异常这时用future.get()
注:关于异常处理的相关案例,已在源码中,这里不做展示
application.properties
//尽量做到每个业务使用自己配置的线程池
service1.thread.coreSize=10
service1.thread.maxSize=100
service1.thread.keepAliveTime=10
复制代码
线程池属性类
/**
* @Description: 线程池属性
* @Author: jianweil
* @date: 2021/12/9 10:44
*/
@ConfigurationProperties(prefix = "service1.thread")
@Data
public class ThreadPoolConfigProperties {
private Integer coreSize;
private Integer maxSize;
private Integer keepAliveTime;
}
复制代码
线程池配置类
/**
* @Description: 线程池配置类:根据不同业务定义不同的线程池配置
**/
@EnableConfigurationProperties(ThreadPoolConfigProperties.class)
@Configuration
public class MyService1ThreadConfig {
@Bean
public ThreadPoolExecutor threadPoolExecutor(ThreadPoolConfigProperties pool) {
return new ThreadPoolExecutor(
pool.getCoreSize(),
pool.getMaxSize(),
pool.getKeepAliveTime(),
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(100000),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
}
}
复制代码
注:本文所有源码已分享github
/**
* @Description: 测试CompletableFuture
* @Author: jianweil
* @date: 2021/12/9 10:50
*/
@SpringBootTest
public class CompletableFutureTest {
@Autowired
private ThreadPoolExecutor threadPoolExecutor;
/***
* 无返回值
* runAsync
*/
@Test
public void main1() {
System.out.println("main.................start.....");
CompletableFuture.runAsync(() -> {
System.out.println("当前线程:" + Thread.currentThread().getId());
int i = 10 / 2;
System.out.println("运行结果:" + i);
}, threadPoolExecutor);
System.out.println("main.................end......");
}
}
复制代码
OutOfMemoryError
错误public class VisiableThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {
private static final Logger logger = LoggerFactory.getLogger(VisiableThreadPoolTaskExecutor.class);
private void showThreadPoolInfo(String prefix) {
ThreadPoolExecutor threadPoolExecutor = getThreadPoolExecutor();
if (null == threadPoolExecutor) {
return;
}
logger.info("{}, {},taskCount [{}], completedTaskCount [{}], activeCount [{}], queueSize [{}]",
this.getThreadNamePrefix(),
prefix,
threadPoolExecutor.getTaskCount(),
threadPoolExecutor.getCompletedTaskCount(),
threadPoolExecutor.getActiveCount(),
threadPoolExecutor.getQueue().size());
}
@Override
public void execute(Runnable task) {
showThreadPoolInfo("1. do execute");
super.execute(task);
}
@Override
public void execute(Runnable task, long startTimeout) {
showThreadPoolInfo("2. do execute");
super.execute(task, startTimeout);
}
@Override
public Future> submit(Runnable task) {
showThreadPoolInfo("1. do submit");
return super.submit(task);
}
@Override
public Future submit(Callable task) {
showThreadPoolInfo("2. do submit");
return super.submit(task);
}
@Override
public ListenableFuture> submitListenable(Runnable task) {
showThreadPoolInfo("1. do submitListenable");
return super.submitListenable(task);
}
@Override
public ListenableFuture submitListenable(Callable task) {
showThreadPoolInfo("2. do submitListenable");
return super.submitListenable(task);
}
}
复制代码
AsyncConfigurer
类的两个方法/**
* @Description: 注解@async配置
* @Author: jianweil
* @date: 2021/12/9 11:52
*/
@Slf4j
@EnableAsync
@Configuration
public class AsyncThreadConfig implements AsyncConfigurer {
/**
* 定义@Async默认的线程池
* ThreadPoolTaskExecutor不是完全被IOC容器管理的bean,可以在方法上加上@Bean注解交给容器管理,这样可以将taskExecutor.initialize()方法调用去掉,容器会自动调用
*
* @return
*/
@Override
public Executor getAsyncExecutor() {
int processors = Runtime.getRuntime().availableProcessors();
//常用的执行器
//ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
//可以查看线程池参数的自定义执行器
ThreadPoolTaskExecutor taskExecutor = new VisiableThreadPoolTaskExecutor();
//核心线程数
taskExecutor.setCorePoolSize(1);
taskExecutor.setMaxPoolSize(2);
//线程队列最大线程数,默认:50
taskExecutor.setQueueCapacity(50);
//线程名称前缀
taskExecutor.setThreadNamePrefix("default-ljw-");
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//执行初始化(重要)
taskExecutor.initialize();
return taskExecutor;
}
/**
* 异步方法执行的过程中抛出的异常捕获
*
* @return
*/
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (ex, method, params) ->
log.error("线程池执行任务发送未知错误,执行方法:{}", method.getName(), ex.getMessage());
}
}
复制代码
默认线程池
/**
* 默认线程池
*/
@Async
public void defaultThread() throws Exception {
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(1000));
long end = System.currentTimeMillis();
int i = 1 / 0;
log.info("使用默认线程池,耗时:" + (end - start) + "毫秒");
}
复制代码
/**
* @Description: 注解@async配置
* @Author: jianweil
* @date: 2021/12/9 11:52
*/
@Slf4j
@EnableAsync
@Configuration
public class AsyncThreadConfig implements AsyncConfigurer {
@Bean("service2Executor")
public Executor service2Executor() {
//Java虚拟机可用的处理器数
int processors = Runtime.getRuntime().availableProcessors();
//定义线程池
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
//可以查看线程池参数的自定义执行器
//ThreadPoolTaskExecutor taskExecutor = new VisiableThreadPoolTaskExecutor();
//核心线程数
taskExecutor.setCorePoolSize(processors);
taskExecutor.setMaxPoolSize(100);
//线程队列最大线程数,默认:100
taskExecutor.setQueueCapacity(100);
//线程名称前缀
taskExecutor.setThreadNamePrefix("my-ljw-");
//线程池中线程最大空闲时间,默认:60,单位:秒
taskExecutor.setKeepAliveSeconds(60);
//核心线程是否允许超时,默认:false
taskExecutor.setAllowCoreThreadTimeOut(false);
//IOC容器关闭时是否阻塞等待剩余的任务执行完成,默认:false(必须设置setAwaitTerminationSeconds)
taskExecutor.setWaitForTasksToCompleteOnShutdown(false);
//阻塞IOC容器关闭的时间,默认:10秒(必须设置setWaitForTasksToCompleteOnShutdown)
taskExecutor.setAwaitTerminationSeconds(10);
/**
* 拒绝策略,默认是AbortPolicy
* AbortPolicy:丢弃任务并抛出RejectedExecutionException异常
* DiscardPolicy:丢弃任务但不抛出异常
* DiscardOldestPolicy:丢弃最旧的处理程序,然后重试,如果执行器关闭,这时丢弃任务
* CallerRunsPolicy:执行器执行任务失败,则在策略回调方法中执行任务,如果执行器关闭,这时丢弃任务
*/
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return taskExecutor;
}
}
复制代码
/**
* 指定线程池service2Executor
*
* @throws Exception
*/
@Async("service2Executor")
public void service2Executor() throws Exception {
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(1000));
long end = System.currentTimeMillis();
log.info("使用线程池service2Executor,耗时:" + (end - start) + "毫秒");
}
复制代码
注:异步任务返回值为void,不能获取的返回值的
例如netty,dubbo这种底层通讯框架通常会参考进行设置
实际情况往往复杂的多,并不会按照这个进行设置
在我们的业务开发中,基本上都是IO密集型,因为往往都会去操作数据库,访问redis,es等存储型组件,涉及到磁盘IO,网络IO。
一个4C8G的机器上部署了一个MQ消费者,在RocketMQ的实现中,消费端也是用一个线程池来消费线程的,那这个线程数要怎么设置呢?
那我们怎么判断需要增加更多线程呢?
《Java 并发编程实战》介绍了一个线程数计算的公式:
如果希望程序跑到CPU的目标利用率,需要的线程数公式为:
如果我期望目标利用率为90%(多核90),那么需要的线程数为:
把公式变个形,还可以通过线程数来计算CPU利用率:
虽然公式很好,但在真实的程序中,一般很难获得准确的等待时间和计算时间,因为程序很复杂,不只是“计算” 。一段代码中会有很多的内存读写,计算,I/O 等复合操作,精确的获取这两个指标很难,所以光靠公式计算线程数过于理想化。
真实程序中的线程数是没有固定答案,先设定预期,比如我期望的CPU利用率在多少,负载在多少,GC频率多少之类的指标后,再通过测试不断的调整到一个合理的线程数
Runtime.getRuntime().availableProcessors()//获取逻辑核心数,如6核心12线程,那么返回的是12
复制代码
# 总核数 = 物理CPU个数 X 每颗物理CPU的核数
# 总逻辑CPU数 = 物理CPU个数 X 每颗物理CPU的核数 X 超线程数
# 查看物理CPU个数
cat /proc/cpuinfo| grep "physical id"| sort| uniq| wc -l
# 查看每个物理CPU中core的个数(即核数)
cat /proc/cpuinfo| grep "cpu cores"| uniq
# 查看逻辑CPU的个数
cat /proc/cpuinfo| grep "processor"| wc -l