一、java 原生线程池
1.ThreadPoolExecutor的重要参数
corePoolSize:核心线程数
核心线程会一直存活,即使没有任务需要执行
当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理
设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭
queueCapacity:任务队列容量(阻塞队列)
当核心线程数达到最大时,新任务会放在队列中排队等待执行
maxPoolSize:最大线程数
当线程数>=corePoolSize,并且<maxPoolSzie,且任务队列已满时。线程池会创建新线程来处理任务
当线程数=maxPoolSize,且任务队列已满时,线程池会拒绝处理任务而抛出异常
keepAliveTime:线程空闲时间
当线程空闲时间达到keepAliveTime时,线程会被回收,直到线程数量=corePoolSize
如果allowCoreThreadTimeout=true,则会直到线程数量=0
allowCoreThreadTimeout:允许核心线程超时
threadFactory:通过这个参数你可以自定义如何创建线程,例如你可以给线程指定一个有意义的名字。
rejectedExecutionHandler:任务拒绝处理器
通过这个参数你可以自定义任务的拒绝策略。如果线程池中所有的线程都在忙碌,并且工作队列也满了(前提是工作队列是有界队列),那么此时提交任务,线程池就会拒绝接收。至于拒绝的策略,你可以通过 handler 这个参数来指定。ThreadPoolExecutor 已经提供了以下 4 种策略。
- CallerRunsPolicy:提交任务的线程自己去执行该任务。
- AbortPolicy:默认的拒绝策略,会 throws RejectedExecutionException。
- DiscardPolicy:直接丢弃任务,没有任何异常抛出。
- DiscardOldestPolicy:丢弃最老的任务,其实就是把最早进入工作队列的任务丢弃,然后把新任务加入到工作队列。
线程池配置:
核心数一般配置为CPU核数 + 1 ,多个的话,线程切换会导致额外的消耗。
最佳线程数 =CPU 核数 * [ 1 +(I/O 耗时 / CPU 耗时)]
可以使用APM工具
2.ThreadPoolExecutor执行顺序:
线程池按以下行为执行任务
当线程数小于核心线程数时,创建线程。
当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
当线程数大于等于核心线程数,且任务队列已满
若线程数小于最大线程数,创建线程
若线程数等于最大线程数,抛出异常,拒绝任务
3.类型
可以使用JDK的线程池工具来创建
Executors.newCachedThreadPool(); //可缓存的线程池
Executors.newFixedThreadPool(); //定长线程池
Executors.newScheduledThreadPool(); //定时
Executors.newSingleThreadExecutor() //单个线程池
ExecutorService executorService = Executors.newCachedThreadPool();
Future submit = executorService.submit(() -> {
System.out.println(12323);
});
while (true){
if(submit.isDone()){ //可以用来判断线程是否执行完成
System.out.println("執行完成");
break;
}
}
一般不建议使用该方法创建线程池,因为默认的使用的都是无界的 LinkedBlockingQueue,高负载情境下,无界队列很容易导致 OOM,而 OOM 会导致所有请求都无法处理,这是致命问题。所以强烈建议使用有界队列。
异常处理:ThreadPoolExecutor 对象的 execute() 方法提交任务时,如果任务在执行的过程中出现运行时异常,会导致执行任务的线程终止;不过,最致命的是任务虽然异常了,但是你却获取不到任何通知,这会让你误以为任务都执行得很正常。虽然线程池提供了很多用于异常处理的方法,但是最稳妥和简单的方案还是捕获所有异常并按需处理,你可以参考下面的示例代码。
为不同的任务创建不同的线程池,如果提交的任务有依赖关系,一定不要提交到相同的线程池。
try {
// 业务逻辑
} catch (RuntimeException x) {
// 按需处理
} catch (Throwable x) {
// 按需处理
}
4 关闭线程池
有俩种方法,有shutdown 和shutdownNow 。 原理是遍历线程中的工作线程,然后逐个调用interrupt方法来中断线程,所以无法响应中断的任务可能永远都不会终止。
shutdown只是将线程池的状态设置为SHUTWDOWN状态,正在执行的任务会继续执行下去,没有被执行的则中断。而shutdownNow则是将线程池的状态设置为STOP,正在执行的任务则被停止,没被执行任务的则返回
5 线程池监控
taskCount: 线程池需要执行的任务数量
completedTaskCount: 线程池运行过程中已完成的任务数量,小鱼或等于taskCount
largerPoolSize: 曾经创建多的最大线程数量,通过该数据可以得知时候曾经满过。
getPoolSize: 线程池的线程个数
getActiveCount:获取活动的线程数
也可以通过重写线程池的一些方法,如 beforeExecute , afterExecute, terminated 方法。
6 ExecutorService 创建的线程池
1 固定线程池, 核心数与最大线程数一样,且使用无界阻塞队列。
由于使用的是无界队列,所以空闲时间以及空闲时间单位是无效的 , 且不会触发拒绝策略。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
2 单个线程池,核心数与最大线程数均为1,且使用无界阻塞队列。 影响同固定线程池, 将所有任务串行化。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
3 缓存线程池
核心数为0 ,最大线程数为Integer的最大值,且使用SynchronousQueue 做为队列。由于SynchronousQueue不存储数据,所有当任务提交时,如果此时没有空余的线程,那么会创建线程,如果完成任务的速度小于提交任务的速度,那么会创建很多的线程,会耗尽CPU和内存资源。
使用SynchronousQueue的目的就是保证“对于提交的任务,如果有空闲线程,则使用空闲线程来处理;否则新建一个线程来处理任务”。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
4 schedule 线程池
可以看到核心线程数为传入的参数, 最大线程数为Integer最大值,且使用DelayedWorkQueue 做为队列(无界)。
DelayedWorkQueue 是一个优先队列,会按照执行时间来进行排序,小的排在前面。
当某个线程从队列中拿到头节点的执行任务,会比较他的执行时间是否大于当前时间,如果大于,则等待,到执行时间苏醒,开始执行任务。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
//最终调用的是ThreadPoolExecutor 构造方法
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
二 手动实现线程池
利用了消费者,生产者模式,创建若干线程,来进行queue的读取。达到线程池的目的
public class MyThreadPool {
private BlockingQueue<Runnable> workQueue;
List<WorkThread> threads;
public MyThreadPool(int poolSize,BlockingQueue<Runnable> workQueue){
this.workQueue = workQueue;
threads = new ArrayList<>(poolSize);
for (int i=0;i<poolSize;i++){
WorkThread work = new WorkThread();
work.start();
threads.add(work);
}
}
void execute(Runnable runnable){
try {
workQueue.put(runnable);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private class WorkThread extends Thread{
@Override
public void run() {
while (true){
try {
Runnable task = workQueue.take();
task.run();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args){
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(2);
MyThreadPool pool = new MyThreadPool(10,workQueue);
pool.execute(()->{
System.out.println("hello");
});
}
}
二、spring 线程池实现
1、进行线程池的配置:
@Configuration
@EnableAsync //需要添加注解一开启对多线程的支持
@ComponentScan(basePackages = "com.test.asyn")
public class AsynConfig {
@Bean
public Executor taskExecutor() { //需要配置一个ExecutorBean
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(20);
executor.setQueueCapacity(30);
executor.setMaxPoolSize(50);
executor.setKeepAliveSeconds(1000);
executor.initialize(); //这里一定要进行初始化
return executor;
}
}
2.实现:
@Service
public class AsynService {
@Async //添加到方法表示这个方法是支持异步的,添加到类上表示类中的全部方法都是异步的
public void exec(int i){
try {
Thread.sleep(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行异步任务" + "=====" + i);
}
}