JDK提供了一个ThreadPoolExecutor类供我们来手动创建线程池,类定义如下:
public class ThreadPoolExecutor extends AbstractExecutorService
1. 构造方法
想要通过ThreadPoolExecutor类来创建线程池,实质上就是向其构造方法传入不同参数来获取不同的线程池。
看具体的构造方法之前,我们先看一下其中会涉及到的参数:
参数名 | 含义 |
---|---|
corePoolSize | 线程池维护线程的最少数量。线程池至少会保持该数量的线程存在,即使没有任务可以处理。(注意:这里说的至少是指线程达到这个数量后,即使有空闲的线程也不会释放,而不是说线程池创建好之后就会初始化这么多线程) |
maximumPoolSize | 线程池最大数量,线程池允许创建的最大线程数,如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程来执行任务。值得注意的是,线程池队列如果使用的是无界队列,那么这个参数就没有什么效果 |
uint | 线程活动保持时间的单位,可选择的时间有时分秒等,即线程池维护线程所允许的空闲时间的单位,和keepAliveTime配合使用 |
keepAliveTime | 线程活动保持时间,线程池的工作线程空闲后,保持存活的时间,所以,当任务很多的时候,并且每个任务执行的时间比较短,可以调大时间,即调大线程活动保持时间,可以提高线程的利用率 |
workQueue | 任务队列,用于暂时保存任务的工作队列 |
threadFactory | 用于创建线程的工厂 |
handler | 饱和策略 |
构造方法如下:
- 使用给定的参数,工作队列,饱和策略以及工厂方法来创建线程池,这是ThreadPoolExecutor最核心的构造方法,其它的构造是在其基础上的重载。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
// 给定的参数非法,抛出异常
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
// 给定的工作队列,工厂方法,饱和策略为空,抛出异常
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
// 设置线程池的安全管理模型
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
// 绑定传入的参数
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
- 使用给定的参数,工作队列,默认的饱和策略和工厂方法来创建线程池。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
- 使用给定的参数,工作队列,工厂方法,默认的饱和策略来创建线程池。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
- 使用给定的参数,工作队列,饱和策略,默认的工厂方法来创建线程池。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
2. 工作队列
在线程池中,一般使用一个队列进行任务和线程池中线程进行耦合,工作队列有如下几种:
- ArrayBlockingQueue:
基于数组结构的有界阻塞队列,此队列按照FIFO(先进先出)原则对元素进行排序。
- LinkedBlockingQueue:
基于链表结构的有界阻塞队列,也按照FIFO排序元素,吞吐量高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool(n)使用了此队列。
- PriorityBlockingQueue:
具有优先级的无限阻塞队列,即优先队列。
- SynchronousQueue:
一个不存储元素的阻塞队列。每个插入操作必须等待另一个线程调用移除操作,否则插入操作一直处于阻塞状态,可以视为只有一个元素的队列。吞吐量要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool()使用了此队列。
3. 饱和策略
饱和策略又称拒绝策略,指的是当线程池中每个线程都在承载线程任务时,这时如果又有了新的线程任务,线程池将会采用的策略,常见线程策略如下:
- AbortPolicy:
该策略是线程池的默认策略。使用该策略时,如果线程池队列满了丢掉这个任务并且抛出RejectedExecutionException异常。
- CallerRunsPolicy:只用调用这所在的线程来运行任务
线程池队列满了,会直接丢弃新加入的任务并且不会抛出异常。
- DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务
如果队列满了,会将最早进入队列的线程任务退出,再尝试将新的任务加入队列。
- DiscardPolicy:
当线程池使用此策略,如果添加到线程池失败(线程池满时),那么主线程将会自己去执行该任务,不会等待线程池中的线程去执行。
- 自定义
JDK允许我们自定义饱和策略,只要实现RejectedExecutionHandler接口,并且实现rejectedExecution方法就可以了,rejectedExecution方法中定义饱和策略的逻辑代码。
4. 手动创建线程池
接下来,使用 ThreadPoolExecutor类进行线程池的创建。
public class MyThreadPool {
/**
* 基本参数
*/
static int corePoolSize = 10;
static int maximumPoolSizeSize = 100;
static long keepAliveTime = 1;
static ArrayBlockingQueue workQueue = new ArrayBlockingQueue(10);
public static void main(String[] args) {
// 使用默认饱和策略创建线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize,
maximumPoolSizeSize,
keepAliveTime,
TimeUnit.SECONDS,
workQueue,
// JDK1.8后此方法作废
new ThreadFactoryBuilder().setNameFormat("XX-task-%d").build());
// 向线程池中添加任务
executor.execute(() -> System.out.println("ok"));
}
}