JAVA为什么需要线程池?该如何实现?


前言

整篇文章将围绕以下三个问题进行阐述

  1. 为什么需要线程池
  2. 不同线程池的种类,各自的特点
  3. 如何实现线程池的配置和使用

1. 为什么我们需要线程池

  • 不使用线程池带来的问题
    1. 线程池默认使用无界队列,任务过多导致 OOM(out of memory)
    2. 线程创建过多,导致 OOM(out of memory)
    3. 共享线程池,次要逻辑拖垮主要逻辑
  • 使用线程池的好处

    1. 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
    2. 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
    3. 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

2. 不同线程池的种类,各自的特点

1、newCaChedThreadPool

创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

这种类型的线程池特点是:

工作线程的创建数量几乎没有限制(其实也有限制的,数目为Interger. MAX_VALUE), 这样可灵活的往线程池中添加线程。

如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的时间(默认为1分钟),则该工作线程将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个工作线程。

在使用CachedThreadPool时,一定要注意控制任务的数量,否则,由于大量线程同时运行,很有会造成系统OOM。

2、newFixedThreadPool

创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。

FixedThreadPool是一个典型且优秀的线程池,它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。但是,在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源。

3、newSingleThreadExecutor

创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。如果这个线程异常结束,会有另一个取代它,保证顺序执行。单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。

4、newScheduleThreadPool

创建一个定长的线程池,而且支持定时的以及周期性的任务执行,支持定时及周期性任务执行。

5、newSingleThreadScheduledExecutor

创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期地执行。线程池中最多执行1个线程,之后提交的线程活动将会排在队列中以此执行并且可定时或者延迟执行线程活动。


3.如何实现线程池的配置和使用

配置

image-20240305004822212

ThreadPoolExecutor的构造函数参数:

  • corePoolSize:核心线程数
  • maximumPoolSize:最大线程数
  • keepAliveTime:空闲线程的最大保活时限
  • TimeUnit:最大保活时限的时间单位
  • BlockingQueue:堵塞队列
  • RejectedExecutionHandler:拒绝处理策略

(ps:这里例子使用的框架为spring)

image-20240305002049836

这里使用FixedThreadPool作为例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* @description: 异步线程池配置
* @author: xsinxcos
* @create: 2024-03-04 23:50
**/
@Configuration
@EnableAsync
public class AsyncThreadPoolConfig {
/**
* CPU 核数
*/
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
/**
* IO 处理线程数
*/
private static final int IO_MAX = Math.max(2, 2 * CPU_COUNT);
/**
* 空闲线程最大保活时限,单位为秒
*/
private static final int KEEP_ALIVE_SECOND = 60;
/**
* 有界阻塞队列容量上限
*/
private static final int QUEUE_SIZE = 10000;

@Bean("asyncExecutor")
public Executor asyncThreadPool(){
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
IO_MAX / 5,
IO_MAX,
KEEP_ALIVE_SECOND,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(QUEUE_SIZE),
new ThreadPoolExecutor.CallerRunsPolicy()
);
return poolExecutor;
}
}

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
@Async("asyncExecutor")
@Override
public void onApplicationEvent(MatchFailMessageEvent event) {
MessageBo matchResultMessage = event.getMatchResultMessage();
//获取三个关键参数 匹配发起者、被邀请者、帖子ID
Long matchFrom = matchResultMessage.getMessage().getSendTo();
Long matchTo = matchResultMessage.getMessage().getSendFrom();
Long matchPost = matchResultMessage.getMessage().getPostId();
//匹配成功,将Redis中的数据删除
String key = "USER:" + matchFrom + " invited USER:" + matchTo + "with " + "POST:" + matchPost;
//删除redis中的数据
redisCache.deleteObject(key);
}

JAVA为什么需要线程池?该如何实现?
https://xsinxcos.github.io/2024/03/04/JAVA为什么需要线程池?该如何实现?/
作者
xsinxcos(涿)
发布于
2024年3月4日
许可协议