通过ThreadPoolExecutor构造函数来创建(推荐)。
通过 Executor 框架的工具类 Executors 来创建。
我们可以创建多种类型的 ThreadPoolExecutor:
Executors.newFixedThreadPool(int):一池N线程. 该方法返回一个固定线程数量的线程池。该线程池中的线程数量始终不变。当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。若没有,则新的任务会被暂存在一个任务队列中,待有线程空闲时,便处理在任务队列中的任务。
ExecutorService threadPool1 = Executors.newFixedThreadPool(5); //5个窗口
Executors.newSingleThreadExecutor():一池一线程. 该方法返回一个只有一个线程的线程池。若多余一个任务被提交到该线程池,任务会被保存在一个任务队列中,待线程空闲,按先入先出的顺序执行队列中的任务。
ExecutorService threadPool2 = Executors.newSingleThreadExecutor(); //一个窗口
Executors.newCachedThreadPool():一池可扩容根据需求创建线程. 该方法返回一个可根据实际情况调整线程数量的线程池。初始大小为0。当有新任务提交时,如果当前线程池中没有线程可用,它会创建一个新的线程来处理该任务。如果在一段时间内(默认为60秒)没有新任务提交,核心线程会超时并被销毁,从而缩小线程池的大小。
ExecutorService threadPool3 = Executors.newCachedThreadPool();
对应 Executors 工具类中的方法如图所示:
执行线程:execute()
关闭线程:shutdown()
void execute(Runnable command);
参数为Runnable接口类,可以通过设置lambda
// 具体案例代码案例
public class ThreadPoolTest {
public static void main(String[] args) {
// 一池N线程
ExecutorService threadPool1 = Executors.newFixedThreadPool(5);
// 一池一线程
ExecutorService threadPool2 = Executors.newSingleThreadExecutor();
// 一池可扩容根据需求创建线程
ExecutorService threadPool3 = Executors.newCachedThreadPool();
// 是个顾客请求
try{
for (int i = 1; i <= 10; i++) {
// 到此时执行execute()方法才创建线程
threadPool2.execute(()->{
System.out.println(Thread.currentThread().getName()+" 办理业务");
});
}
}finally {
// 关闭线程
threadPool1.shutdown();
}
}
}
一池N线程输出结果为:
pool-1-thread-2 办理业务
pool-1-thread-1 办理业务
pool-1-thread-3 办理业务
pool-1-thread-5 办理业务
pool-1-thread-4 办理业务
pool-1-thread-2 办理业务
pool-1-thread-1 办理业务
pool-1-thread-4 办理业务
pool-1-thread-5 办理业务
pool-1-thread-3 办理业务
一池一线程输出结果为:
pool-2-thread-1 办理业务
pool-2-thread-1 办理业务
pool-2-thread-1 办理业务
pool-2-thread-1 办理业务
pool-2-thread-1 办理业务
pool-2-thread-1 办理业务
pool-2-thread-1 办理业务
pool-2-thread-1 办理业务
pool-2-thread-1 办理业务
pool-2-thread-1 办理业务
一池可扩容根据需求创建线程输出结果为:
pool-3-thread-1 办理业务
pool-3-thread-5 办理业务
pool-3-thread-4 办理业务
pool-3-thread-7 办理业务
pool-3-thread-2 办理业务
pool-3-thread-3 办理业务
pool-3-thread-8 办理业务
pool-3-thread-6 办理业务
pool-3-thread-9 办理业务
pool-3-thread-10 办理业务
《阿里巴巴JAVA开发手册》有这样一条强制规定:线程池不允许使用Executors去创建,而应该通过ThreadPoolExecutor方式,这样处理方式更加明确线程池运行规则,规避资源耗尽风险。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
上面这两种方式创建线程池使用的阻塞队列是LinkedBlockingQueue
/**
* A constant holding the maximum value an {@code int} can
* have, 231-1.
*/
//2的31次方,然后在减1 2147483647
@Native public static final int MAX_VALUE = 0x7fffffff;
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
不设大小理论上队列容量无上限,所以可能会堆积大量请求从而导致OOM。
以上这种方式,就不聊了。
那么在项目中如何去使用线程池呢?
在项目中,我们通常有两种方式创建线程池:
比如说我们项目中需要处理用户登录日志,但是此时不想因为记录登录日志耽搁了登录。
如果我们使用同步的方式,可能会因为一些不太需要实时结果的,并且又耗时的业务可能会导致整个业务变慢:
耗时:200ms=100ms+100ms
如果使用线程池做了异步化后,直接创建个任务丢到线程池里,这样就减少了后面那100ms的等待时间。
在实际项目中,也有很多项目使用消息队列来做异步化,这个看项目情况来,比如:开发成本、后期运维成本等。
静态方式就是当做一种工具类使用,代码实现如下:
package com.tianwc.myblog.pool;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
//这里的相关只是一个演示,大家在定参数时,还是要以具体情况来
public class ThreadPoolUtil {
//获取CPU核数
static int cpuNums = Runtime.getRuntime().availableProcessors();
/** 线程池核心池的大小*/
private static int corePoolSize = 10;
/** 线程池的最大线程数*/
private static int maximumPoolSize = cpuNums * 5;
//阻塞队列容量
private static int queueCapacity = 100;
//活跃时间,
private static int keepAliveTimeSecond = 300;
public static ExecutorService httpApiThreadPool = null;
static{
System.out.println("创建线程数:"+corePoolSize+",最大线程数:"+maximumPoolSize);
//建立10个核心线程,线程请求个数超过20,则进入队列等待
httpApiThreadPool = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTimeSecond,
TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(queueCapacity),new ThreadFactoryBuilder().setNameFormat("PROS-%d").build());
}
}
在业务代码中的使用
ThreadPoolUtil.httpApiThreadPool.submit(new Thread(new Runnable() {
@Override
public void run() {
System.out.println("======== 登录日志记录=====start=======");
try {
// TODO: 2022/4/14 业务处理
Thread.sleep(1000L);
System.out.println("userId=" + userId);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("========登录日志记录------end=======");
}
}));
但是这种方式存在很多问题,很多项目也是这么在用的。比如想动态修改线程池参数,这种方式就不好处理了
我们再来看看Spring Boot创建方式;
配置文件, 我们可以把线程池相关参数配置在配置文件中application.yaml(或者application.properties)。
threadpool:
corePoolSize: 8 # 获取cpu核数
maxPoolSize: 16 # 线程池最大线程数
queueCapacity: 5 # 阻塞队列容量
keepAliveSeconds: 300 # 活跃时间
然后创建线程池:
package com.tianwc.myblog.pool;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import javax.annotation.Resource;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
@EnableAsync // 开启异步请求 在项目中通常是将注解 @EnableAsync 放到项目启动类上
public class ThreadPoolConfig {
@Resource
private Environment env;
//创建线程池
@Bean("taskExecutor")
public ThreadPoolTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor pool = new ThreadPoolTaskExecutor();
pool.setThreadNamePrefix("--------------全局线程池-----------------");
pool.setCorePoolSize(Integer.parseInt(env.getProperty("threadpool.corePoolSize")));
pool.setMaxPoolSize(Integer.parseInt(env.getProperty("threadpool.maxPoolSize")));
pool.setKeepAliveSeconds(Integer.parseInt(env.getProperty("threadpool.queueCapacity")));
pool.setQueueCapacity(Integer.parseInt(env.getProperty("threadpool.keepAliveSeconds")));
// 直接在execute方法的调用线程中运行
pool.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 初始化
pool.initialize();
return pool;
}
}
在项目中使用:
package com.tianwc.myblog.pool;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@Component
public class AsynchronousTask {
@Async("taskExecutor")
public void recordLoginLog(Long userId){
System.out.println("======== 登录日志记录=====start=======");
try {
// TODO: 2022/4/14 业务处理
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("========登录日志记录------end=======");
}
}
然后在登录的代码中使用:
@Resource
private AsynchronousTask asynchronousTask;
public Boolean login(){
//登录处理
Long userId=10001;
boolean isSucc=true;
if(isSucc){
asynchronousTask.recordLoginLog(userId);
}
System.out.println("=====登录成功====");
}
输出日志:
=====登录成功====
======== 登录日志记录=====start=======
userId=10001
========登录日志记录------end=======
以上就是项目中通常使用的方式,另外,注意,在项目中通常是将注解@EnableAsync放到项目启动类上。
下面这个链接中介绍了使用Spring的使用线程池的案例, 可以看看进行参考
https://blog.csdn.net/lingerlan510/article/details/122322588
面试官:在项目中,你是如何使用线程池的?