创建线程池的方法有两种,一种是手动创建,一种是自动创建,本文将分别介绍这两种方式的简单使用。
一、自动创建线程池
自动创建线程池主要是调用jdk提供的Executors
类提供的方法。
1、newFixedThreadPool()方法
它创建一个可重用固定个数的线程池,以共享的无界队列运行这些线程,构造方法如下:
/* @param nThreads the number of threads in the pool
* @return the newly created thread pool
* @throws IllegalArgumentException if {@code nThreads <= 0}
*/
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
此方法建了一个核心线程数和最大线程数相等的线程池,它的数量也就是我们传入的参数,需要注意的是,LinkedBlockingQueue队列容量是2147483647
,假如任务的处理速度比较慢,那么随着请求的增多,队列中堆积的任务也会越来越多,最终这些任务占用大量内存,会发生内存泄漏,会造成很严重的后果。
2、newSingleThreadExecutor()方法
它创建一个单线程的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO,LIFO)执行,构造方法如下:
/*
@return the newly created single-threaded Executor
*/
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
它和第一种方法的隐患是一样的,只不过在构造时将核心线程数和最大线程数直接设置成了1,但是任务队列仍是无界的LinkedBlockingQueue,仍可能会造成内存泄漏问题。
它是可缓存线程池,先查看池中有没有以前建立的线程,如果有就直接使用,如果没有就创建一个新的线程加入池中,通常用于执行一些生存期很短的异步型任务,构造方法如下:
/*
* @return the newly created thread pool
*/
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
这个构造方法中使用的任务队列是SynchronousQueue,SynchronousQueue本身并不存储任务,而是对任务直接进行转发。但它的最大线程数被设置成了Integer.MAX_VALUE
,由于CachedThreadPool 并不限制线程的数量,当任务数量特别多的时候,就可能会导致创建非常多的线程,最终超过了操作系统的上限而无法创建新线程,或者导致内存不足。
4、newScheduledThreadPool()方法
它创建一个定长线程池,支持定时及周期性任务执行,构造方法如下:
/**
* Creates a thread pool that can schedule commands to run after a
* given delay, or to execute periodically.
* @param corePoolSize the number of threads to keep in the pool,
* even if they are idle
* @return a newly created scheduled thread pool
* @throws IllegalArgumentException if {@code corePoolSize < 0}
*/
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
/**
* Creates a new {@code ScheduledThreadPoolExecutor} with the
* given core pool size.
*
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @throws IllegalArgumentException if {@code corePoolSize < 0}
*/
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
5、newSingleThreadScheduledExecutor()方法
它的构造方法如下:
/*
*@return the newly created scheduled executor
*/
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1));
}
第四和第五种方式都是使用ScheduledThreadPoolExecutor(int corePoolSize)
作为核心构造方法,其实质是一种延迟队列,同时也定义了最大线程数为Integer.MAX_VALUE
,同样有可能会出现内存泄漏的状况。
二、自定义线程池
1、ThreadPoolExecutor构造函数
/**
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters and default thread factory.
*
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @param maximumPoolSize the maximum number of threads to allow in the
* pool
* @param keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating.
* @param unit the time unit for the {@code keepAliveTime} argument
* @param workQueue the queue to use for holding tasks before they are
* executed. This queue will hold only the {@code Runnable}
* tasks submitted by the {@code execute} method.
* @param handler the handler to use when execution is blocked
* because the thread bounds and queue capacities are reached
* @throws IllegalArgumentException if one of the following holds:<br>
* {@code corePoolSize < 0}<br>
* {@code keepAliveTime < 0}<br>
* {@code maximumPoolSize <= 0}<br>
* {@code maximumPoolSize < corePoolSize}
* @throws NullPointerException if {@code workQueue}
* or {@code handler} is null
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
各参数的含义如下:
-
corePoolSize
:核心线程数,即使空闲时仍保留在池中的线程数,除非设置allowCoreThreadTimeOut
-
maximumPoolSize
: 池中允许创建的最大线程数 -
keepAliveTime
:当线程数大于内核时,新进的任务会被存储到队列中,当这些线程等待时间超过这个阈值,就会被回收,等待执行新任务。 -
unit
:keepAliveTime
参数的时间单位 -
workQueue
: 用于在执行任务之前使用的队列。这个队列将仅保存execute方法提交的Runnable任务。 -
handler
:阻止策略,执行被阻止时使用的处理程序,因为达到线程限制和队列容量
线程池的执行逻辑是这样的:
- 当线程池接收到提交的新任务,首先判断核心线程数目是否已经达到了限定值,如果没有,不论其它工作线程是否处于空闲状态,线程池都会创建一个新线程处理该任务
- 当核心线程数目已满足限定值,工作队列未满时,线程池会将新任务放入执行队列进行等待
- 当工作队列已满,当前线程数超过核心线程数,小于允许创建的最大线程数时,线程池会创建一个新线程处理该任务
- 若当前线程数超过允许创建的最大线程数时,线程池会按照指定的策略处理无法执行的任务
2、BlockingQueue队列
一般分为直接执行队列、有界任务队列、无界任务队列、优先任务队列。
(1)SynchronousQueue直接执行队列:SynchronousQueue是一个特殊的BlockingQueue,它没有容量,每执行一个插入操作就会阻塞。
使用SynchronousQueue队列,提交的任务不会被保存,总是会马上提交执行。如果用于执行任务的线程数量小于maximumPoolSize,则尝试创建新的进程,如果达到maximumPoolSize设置的最大值,则根据设置的handler执行拒绝策略。
(2)ArrayBlockingQueue有界队列:当有新的任务需要执行,线程池会创建新的线程,直到创建的线程数量达到corePoolSize时,则会将新的任务加入到等待队列中。若等待队列已满,即超过ArrayBlockingQueue初始化的容量,则继续创建线程,直到线程数量达到maximumPoolSize设置的最大线程数量,若大于maximumPoolSize,则执行拒绝策略。
它的执行逻辑与前面提到的部分基本一致,值得注意的是,如果队列初始容量较大或者没有达到超负荷的状态,线程数将一直维持在corePoolSize以下。
(3)LinkedBlockingQueue无界队列:无界队列的构造函数如下,LinkedBlockingQueue队列容量是2147483647,前面提到,假如任务的处理速度比较慢,那么随着请求的增多,队列中堆积的任务也会越来越多,最终这些任务占用大量内存,使用时要做好处理,避免出现资源耗尽的情况。
/**
* Creates a {@code LinkedBlockingQueue} with a capacity of
* {@link Integer#MAX_VALUE}.
*/
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
/**
* Creates a {@code LinkedBlockingQueue} with the given (fixed) capacity.
*
* @param capacity the capacity of this queue
* @throws IllegalArgumentException if {@code capacity} is not greater
* than zero
*/
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
last = head = new Node<E>(null);
}
(4)PriorityBlockingQueue优先任务队列:该队列的默认数组大小是11,但是其允许的最大容量也是很大的。在构造时,可以传入一个比较器Comparator,用来比较队列内各任务的优先级。
/**
* 默认数组大小.
*/
private static final int DEFAULT_INITIAL_CAPACITY = 11;
/**
* 最大数组大小.
* Some VMs reserve some header words in an array.
* Attempts to allocate larger arrays may result in
* OutOfMemoryError: Requested array size exceeds VM limit
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**构造函数
* Creates a {@code PriorityBlockingQueue} with the specified
* initial capacity that orders its elements according to their
* {@linkplain Comparable natural ordering}.
*/
public PriorityBlockingQueue(int initialCapacity) {
this(initialCapacity, null);
}
public PriorityBlockingQueue(int initialCapacity,
Comparator<? super E> comparator) {
if (initialCapacity < 1)
throw new IllegalArgumentException();
this.lock = new ReentrantLock();
this.notEmpty = lock.newCondition();
this.comparator = comparator;
this.queue = new Object[initialCapacity];
}
3、Reject阻止策略
当提交的任务数大于(workQueue.size() + maximumPoolSize),就会触发线程池的拒绝策略。
RejectedExecutionHandler 是拒绝策略的接口,有四个实现类
- AbortPolicy策略:该策略会直接抛出异常,阻止系统正常工作;
- CallerRunsPolicy策略:如果线程池的线程数量达到上限,该策略会把任务队列中的任务放在调用者线程当中运行,即从哪个线程来的,哪个线程处理,;
- DiscardOledestPolicy策略:该策略会丢弃任务队列中最老的一个任务,也就是当前任务队列中最先被添加进去的,马上要被执行的那个任务,并尝试再次提交;
- DiscardPolicy策略:该策略会默默丢弃无法处理的任务,不予任何处理。当然使用此策略,业务场景中需允许任务的丢失;
public interface RejectedExecutionHandler {
/**
* @param r the runnable task requested to be executed
* @param executor the executor attempting to execute this task
* @throws RejectedExecutionException if there is no remedy
*/
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
4、自定义线程池实现
(1)模拟自定义拒绝策略,实现RejectedExecutionHandler
public class MyRejected implements RejectedExecutionHandler{
public MyRejected(){
}
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("当前被拒绝任务为:" + r.toString());
}
}
(2)task任务类
public class MyTask implements Runnable {
private int taskId;
private String taskName;
public MyTask(int taskId, String taskName){
this.taskId = taskId;
this.taskName = taskName;
}
/**
此处省略get/set方法
**/
@Override
public void run() {
try {
System.out.println("run taskId =" + this.taskId);
Thread.sleep(5*1000);
System.out.println("end taskId =" + this.taskId);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public String toString(){
return Integer.toString(this.taskId);
}
}
(3)自定义线程池实现
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(
1, //coreSize
2, //MaxSize
60, //60
TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(3) //指定一种队列 (有界队列)
, new MyRejected() //自定义拒绝策略
//, new DiscardOldestPolicy() //可以使用默认拒绝策略
);
MyTask mt1 = new MyTask(1, "任务1");
MyTask mt2 = new MyTask(2, "任务2");
MyTask mt3 = new MyTask(3, "任务3");
MyTask mt4 = new MyTask(4, "任务4");
MyTask mt5 = new MyTask(5, "任务5");
MyTask mt6 = new MyTask(6, "任务6");
pool.execute(mt1);
pool.execute(mt2);
pool.execute(mt3);
pool.execute(mt4);
pool.execute(mt5);
pool.execute(mt6);
pool.shutdown();
}
控制台输出如下:
run taskId =5
当前被拒绝任务为:6
run taskId =1
run taskId =2
run taskId =3
run taskId =4
分析如下:
(1)当第1个任务到达时,线程池创建一个新线程去处理该任务
(2)当第2个任务到达时,当前线程核心线程数已满足,但是未满足最大线程数,因此线程池会再创建一个新线程去处理该任务
(3)当第3/4/5个任务到达时,因已满足最大线程数,线程池将它们放入队列等候处理
(4)当第6个任务到达时,因已满足最大线程数,且队列已满,线程池无法处理该任务,执行自定义拒绝策略
三、springboot使用自定义线程池
1、配置线程池
@Configuration
@EnableAsync //开启多线程
public class ThreadPoolConfig {
@Bean("taskExecutor")
public Executor asyncServiceExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 设置核心线程数
executor.setCorePoolSize(5);
// 设置最大线程数
executor.setMaxPoolSize(20);
//配置队列大小
executor.setQueueCapacity(Integer.MAX_VALUE);
// 设置线程空闲时间,当超过核心线程之外的线程在空闲到达之后会被销毁(秒)
executor.setKeepAliveSeconds(60);
// 设置默认线程名称
executor.setThreadNamePrefix("ThreadExcutor");
// 等待所有任务结束后再关闭线程池
executor.setWaitForTasksToCompleteOnShutdown(true);
//拒绝策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
//执行初始化
executor.initialize();
return executor;
}
}
2、为@Async指定线程池
@Component
@Async("taskExecutor") //要和线程池bean中的对应
public class ThreadService {
//期望此操作在线程池执行 ,不会影响原有的主线程
try {
Thread.sleep(5000);
System.out.println("业务处理完成了....");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}