自定义线程池 3.0
1. 简介
上次我们实现了自定线程池的 2.1 版本,让使用者决定临时线程等待任务的最长时间和任务队列,增强了线程池的灵活性,本文将对线程池做如下的优化:
- 当 线程数量等于最大线程数量 且 任务队列已满 时,提交任务需要做相应的处理。
2. 优化
当 线程数量等于最大线程数量 且 任务队列已满 时,提交任务有多种处理方,以下将其称为 拒绝策略,这里不妨用一点时间思考一下。
我想到的策略有以下几种:
- 抛出异常。
- 直接丢弃这个任务。
- 丢弃队列中最旧的任务,重新提交任务。
- 让提交任务的线程去执行这个任务。
- 新建一个线程来执行这个任务。
这里先想一想拒绝时应该传什么参数:
- 如果想要在拒绝的方法中重新执行任务,那么被拒绝的任务是必需的。
- 如果想丢弃队列中最旧的任务,那么似乎应该把任务队列传到方法中,但这样不好,因为把任务队列直接暴露给拒绝方法后,拒绝方法可能对任务队列进行其他操作,不安全,封装性不好。这里建议把给线程池实现一个丢弃最旧任务的成员方法,然后线程池对象传入拒绝方法中,拒绝方法就只能调用这个方法对任务队列进行操作了,这样封装性更强。
3. 策略的实现方式
3.1 多个方法 + 枚举类
接下来就该实现这几种策略了,一般来说,可以直接写几个方法,然后再写一个枚举类,让使用者指定拒绝策略,伪代码如下所示:
/* 这些方法/枚举类写在 ThreadPool3_0 外 */
public static void throwException(ThreadPool3_0 threadPool, Runnable task) {...}
public static void discard(ThreadPool3_0 threadPool, Runnable task) {...}
public static void discardOldest(ThreadPool3_0 threadPool, Runnable task) {...}
public static void callerRun(ThreadPool3_0 threadPool, Runnable task) {...}
public static void newThreadRun(ThreadPool3_0 threadPool, Runnable task) {...}
enum RejectPolicy {
THROW_EXCEPTION, DISCARD, DISCARD_OLDEST, CALLER_RUN, NEW_THREAD_RUN
}
/* 以下内容写在 ThreadPool3_0 里 */
private final RejectPolicy rejectPolicy;
public ThreadPool3_0(..., RejectPolicy rejectPolicy) {
...
this.rejectPolicy = rejectPolicy;
}
public void submit(Runnable task) {
...
// 线程数量到达最大线程数量,任务队列已满,执行拒绝策略
switch (rejectPolicy) {
case THROW_EXCEPTION: throwException(this, task); break;
case DISCARD: discard(this, task); break;
case DISCARD_OLDEST: discardOldest(this, task); break;
case CALLER_RUN: callerRun(this, task); break;
case NEW_THREAD_RUN: newThreadRun(this, task); break;
}
}
如果使用者自定义一种策略,需要修改 ThreadPool3_0
的 submit()
方法的源代码,增加一个分支,显然不可能。
3.2 策略模式
如果使用了 OOP 的 多态 特性,扩展性会很强,伪代码如下:
public interface RejectPolicy {
void rejectPolicy(ThreadPool3_0 threadPool, Runnable task);
}
public class ThrowExceptionPolicy implements RejectPolicy {...}
public class DiscardPolicy implements RejectPolicy {...}
public class DiscardOldestPolicy implements RejectPolicy {...}
public class CallerRunPolicy implements RejectPolicy {...}
public class NewThreadRunPolicy implements RejectPolicy {...}
public class ThreadPool3_0 {
...
private final RejectPolicy rejectPolicy;
public ThreadPool3_0(..., RejectPolicy rejectPolicy) {
...
this.rejectPolicy = rejectPolicy;
}
public void submit(Runnable task) {
...
// 线程数量到达最大线程数量,任务队列已满,执行拒绝策略
rejectPolicy.reject(this, task);
}
...
}
实际上这段伪代码有 策略模式 的思想:封装一个策略的接口,分别实现具体的策略子类。如果要切换策略,直接切换对象即可;如果要新增策略,直接实现策略接口即可。相比之下,策略模式完胜。
4. 实现
4.1 ThrowExceptionPolicy
public class ThrowExceptionPolicy implements RejectPolicy {
@Override
public void reject(ThreadPool3_0 threadPool, Runnable task) {
throw new RuntimeException("线程数量达到最大数量,任务队列已满,无法执行任务");
}
}
4.2 DiscardPolicy
public class DiscardPolicy implements RejectPolicy {
@Override
public void reject(ThreadPool3_0 threadPool, Runnable task) {
// 这里不作为
}
}
4.3 DiscardOldestPolicy
public class DiscardOldestPolicy implements RejectPolicy {
@Override
public void reject(ThreadPool3_0 threadPool, Runnable task) {
threadPool.discardOldestTask();
threadPool.submit(task);
}
}
4.4 CallerRunPolicy
public class CallerRunPolicy implements RejectPolicy {
@Override
public void reject(ThreadPool3_0 threadPool, Runnable task) {
task.run();
}
}
4.5 NewThreadRunPolicy
public class NewThreadRunPolicy implements RejectPolicy {
@Override
public void reject(ThreadPool3_0 threadPool, Runnable task) {
new Thread(task, "thread-for-rejected-task").start();
}
}
4.6 RejectPolicy
public interface RejectPolicy {
void reject(ThreadPool3_0 threadPool, Runnable task);
// 这里创建这几个常量对象,使用时直接传入常量对象即可,无需新建对象,实现对象的复用,好处与单例模式类似
RejectPolicy THROW_EXCEPTION = new ThrowExceptionPolicy();
RejectPolicy DISCARD = new DiscardPolicy();
RejectPolicy DISCARD_OLDEST = new DiscardOldestPolicy();
RejectPolicy CALLER_RUN = new CallerRunPolicy();
RejectPolicy NEW_THREAD_RUN = new NewThreadRunPolicy();
}
4.7 ThreadPool3_0
public class ThreadPool3_0 {
/**
* 线程池中核心线程的最大数量
*/
private final int corePoolSize;
/**
* 线程池中线程的最大数量
*/
private final int maxPoolSize;
/**
* 临时线程阻塞的最长时间(单位:ns),超过这个时间还没有领取到任务就直接退出
*/
private final long keepAliveTime;
/**
* 任务队列
*/
private final BlockingQueue<Runnable> taskQueue;
/**
* 拒绝策略,用于在无法执行任务的时候拒绝任务
*/
private final RejectPolicy rejectPolicy;
/**
* 构造一个线程池
*
* @param corePoolSize 线程池中核心线程的最大数量
* @param maxPoolSize 线程池中线程的最大数量
* @param keepAliveTime 临时线程阻塞的最长时间
* @param unit 时间的单位
* @param taskQueue 任务队列
* @param rejectPolicy 拒绝策略
*/
public ThreadPool3_0(int corePoolSize, int maxPoolSize, long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> taskQueue, RejectPolicy rejectPolicy) {
this.corePoolSize = corePoolSize;
this.maxPoolSize = maxPoolSize;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.taskQueue = taskQueue;
this.rejectPolicy = rejectPolicy;
}
/**
* 存放线程的集合,使用 {@link Set} 是因为 {@link Set#remove(Object)} 性能更高
*/
private final Set<Worker> threadPool = new HashSet<>();
/**
* 线程池的管程
* <p>
* 用于保证 <strong>获取线程池大小</strong>、<strong>将线程放入线程池</strong>、<strong>从线程池中移除线程</strong> 的互斥性
*/
private final Object threadPoolMonitor = new Object();
/**
* <h3>核心线程执行的任务</h3>
* {@link #getTask()} 方法会一直阻塞,直到有新任务
*/
public final class CoreWorker extends Worker {
public CoreWorker(Runnable initialTask, Set<Worker> threadPool) {
super(initialTask, threadPool);
}
@Override
protected Runnable getTask() {
try {
return taskQueue.take();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
@Override
protected void onWorkerExit() {
// 在目前的代码中,发现核心线程并不会退出,所以这个方法先不实现
}
}
/**
* <h3>临时线程执行的任务</h3>
* {@link #getTask()} 方法会在阻塞一定时间后如果还没有任务,则会返回 {@code null}
*/
public final class TempWorker extends Worker {
public TempWorker(Runnable initialTask, Set<Worker> threadPool) {
super(initialTask, threadPool);
}
@Override
protected Runnable getTask() {
try {
return taskQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
@Override
protected void onWorkerExit() {
removeWorkerFromThreadPool(this);
}
}
/**
* 提交任务
*
* @param task 待执行的任务
*/
public void submit(Runnable task) {
// 如果 线程数量 小于 最大核心线程数量,则新建一个 核心线程 执行任务,然后直接返回
synchronized (threadPoolMonitor) {
if (threadPool.size() < corePoolSize) {
CoreWorker coreWorker = new CoreWorker(task, threadPool);
coreWorker.start();
return;
}
}
// 如果能够放到任务队列中,则直接返回
if (taskQueue.offer(task)) {
return;
}
// 如果 线程数量 小于 最大线程数量,则新建一个 临时线程 执行任务
synchronized (threadPoolMonitor) {
if (threadPool.size() < maxPoolSize) {
TempWorker tempWorker = new TempWorker(task, threadPool);
tempWorker.start();
return;
}
}
// 线程数量到达最大线程数量,任务队列已满,执行拒绝策略
rejectPolicy.reject(this, task);
}
/**
* 获取当前线程池中的线程数量
*
* @return 当前线程池中的线程数量
*/
public int getCurrPoolSize() {
synchronized (threadPoolMonitor) {
return threadPool.size();
}
}
/**
* 丢弃任务队列 {@link #taskQueue} 中的最旧的任务(队头任务)
*
* @return 任务队列中的最旧的任务(队头任务)
*/
public Runnable discardOldestTask() {
return taskQueue.poll();
}
/**
* 从 {@link #threadPool} 中移除指定的 {@link Worker} 对象
*
* @param worker 待移除的 {@link Worker} 对象
*/
private void removeWorkerFromThreadPool(Worker worker) {
synchronized (threadPoolMonitor) {
threadPool.remove(worker);
}
}
}
5. 测试程序
public class ThreadPool3_0Test {
/**
* 测试线程池 3.0 版本的基本功能
*/
@Test
public void test() throws InterruptedException {
final int taskSize = 3;
CountDownLatch latch = new CountDownLatch(taskSize);
ThreadPool3_0 threadPool = new ThreadPool3_0(1, 2, 3, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3), RejectPolicy.DISCARD);
LogUtil.infoWithTimeAndThreadName("提交任务前");
for (int i = 0; i < taskSize; i++) {
int finalI = i;
threadPool.submit(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
LogUtil.infoWithTimeAndThreadName("正在执行任务" + finalI);
latch.countDown();
});
}
LogUtil.infoWithTimeAndThreadName("提交任务后");
// 等待测试结束
latch.await();
LogUtil.infoWithTimeAndThreadName("任务执行完毕");
}
/**
* 测试线程池 3.0 版本的 抛出异常的拒绝策略 的功能
*/
@Test
public void testThrowExceptionPolicy() {
Assertions.assertThrows(RuntimeException.class, () -> {
final int taskSize = 3;
ThreadPool3_0 threadPool = new ThreadPool3_0(1, 1, 3, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1), RejectPolicy.THROW_EXCEPTION);
for (int i = 0; i < taskSize; i++) {
threadPool.submit(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
}
});
LogUtil.infoWithTimeAndThreadName("没有其他报错,断言成功,这段代码抛出了 RuntimeException,异常被 Assertions.assertThrows 吃掉了");
}
/**
* 测试线程池 3.0 版本的 直接丢弃任务的拒绝策略 的功能
*/
@Test
public void testDiscardPolicy() throws InterruptedException {
final int taskSize = 3;
AtomicInteger count = new AtomicInteger(0);
ThreadPool3_0 threadPool = new ThreadPool3_0(1, 1, 3, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1), RejectPolicy.DISCARD);
for (int i = 0; i < taskSize; i++) {
int finalI = i;
threadPool.submit(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
LogUtil.infoWithTimeAndThreadName("正在执行任务" + finalI);
count.getAndIncrement();
});
}
// 等待 3s,确保不是因为执行时间不够导致无法执行 3 个任务
Thread.sleep(3000);
LogUtil.infoWithTimeAndThreadName("共执行了["+count.get()+"]个任务");
}
/**
* 测试线程池 3.0 版本的 丢弃最旧任务、重新提交任务的拒绝策略 的功能
*/
@Test
public void testDiscardOldestPolicy() throws InterruptedException {
final int taskSize = 3;
AtomicInteger count = new AtomicInteger(0);
ThreadPool3_0 threadPool = new ThreadPool3_0(1, 1, 3, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1), RejectPolicy.DISCARD_OLDEST);
for (int i = 0; i < taskSize; i++) {
int finalI = i;
threadPool.submit(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
LogUtil.infoWithTimeAndThreadName("正在执行任务" + finalI);
count.getAndIncrement();
});
}
// 等待 3s,确保不是因为执行时间不够导致无法执行 3 个任务
Thread.sleep(3000);
LogUtil.infoWithTimeAndThreadName("共执行了["+count.get()+"]个任务");
}
/**
* 测试线程池 3.0 版本的 提交任务线程执行任务的拒绝策略 的功能
*/
@Test
public void testCallerRunPolicy() throws InterruptedException {
final int taskSize = 3;
CountDownLatch latch = new CountDownLatch(taskSize);
ThreadPool3_0 threadPool = new ThreadPool3_0(1, 1, 3, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1), RejectPolicy.CALLER_RUN);
for (int i = 0; i < taskSize; i++) {
int finalI = i;
threadPool.submit(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
LogUtil.infoWithTimeAndThreadName("正在执行任务" + finalI);
latch.countDown();
});
}
latch.await();
}
/**
* 测试线程池 3.0 版本的 新建一个线程执行任务的拒绝策略 的功能
*/
@Test
public void testNewThreadRunPolicy() throws InterruptedException {
final int taskSize = 3;
CountDownLatch latch = new CountDownLatch(taskSize);
ThreadPool3_0 threadPool = new ThreadPool3_0(1, 1, 3, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1), RejectPolicy.NEW_THREAD_RUN);
for (int i = 0; i < taskSize; i++) {
int finalI = i;
threadPool.submit(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
LogUtil.infoWithTimeAndThreadName("正在执行任务" + finalI);
latch.countDown();
});
}
latch.await();
}
}
6. 测试结果
6.1 test
这个属于常规测试,读者应该很熟悉了。
14:21:55 [ main] 提交任务前
14:21:55 [ main] 提交任务后
14:21:56 [Thread-3] 正在执行任务0
14:21:57 [Thread-3] 正在执行任务1
14:21:58 [Thread-3] 正在执行任务2
14:21:58 [ main] 任务执行完毕
6.2 testThrowExceptionPolicy
如果直接运行 Assertions.assertThrows()
中的代码会抛出异常,所以使用 assertThrows()
断言这段代码运行时一定会抛出 RuntimeException
,只要没有报错就成功了。
14:21:53 [ main] 没有其他报错,断言成功,这段代码抛出了 RuntimeException,异常被 Assertions.assertThrows 吃掉了
6.3 testDiscardPolicy
这里只执行任务 0 和 1,任务2 直接被丢弃。
14:21:51 [Thread-0] 正在执行任务0
14:21:52 [Thread-0] 正在执行任务1
14:21:53 [ main] 共执行了[2]个任务
6.4 testDiscardOldestPolicy
这里只执行任务 0 和 2,由于 任务1 在任务队列头部,提交 任务2 时将其丢弃,放入 任务2。
14:21:59 [Thread-4] 正在执行任务0
14:22:00 [Thread-4] 正在执行任务2
14:22:01 [ main] 共执行了[2]个任务
6.5 testCallerRunPolicy
这里执行了 3 个任务,但有一个任务是提交任务的线程(即主线程 main
)执行的。
14:21:54 [ main] 正在执行任务2
14:21:54 [Thread-2] 正在执行任务0
14:21:55 [Thread-2] 正在执行任务1
6.6 testNewThreadRunPolicy
这里执行了 3 个任务,但有一个任务是新建的线程(即线程 thread-for-rejected-task
)执行的。
14:22:02 [Thread-5] 正在执行任务0
14:22:02 [thread-for-rejected-task] 正在执行任务2
14:22:03 [Thread-5] 正在执行任务1
7. 思考
DiscardPolicy
直接丢弃任务,和之前版本的直接丢弃没有区别,在当前版本中既然要避免用户浑然不知的情况,那为什么要提出这个策略?NewThreadRunPolicy
新建一个线程去执行任务,如果新建操作很频繁,那么性能会下降很多,这个策略的适用场景是什么?RejectPolicy
中创建了 5 个常量对象,好处是什么?它是单例模式吗?- 我们之前创建的线程的名字默认是
thread-?
,这样在调试多线程程序时不直观,你能想办法优化一下吗? - 如果增加了给线程起名字的功能,构造方法会变得很臃肿,如果只是想简单创建一个线程池并使用,还必须得传递很多参数,有没有传递更少参数的做法?
8. 总结
这次我们实现了自定义线程池的 3.0 版本,提供了拒绝策略,从而让使用者感知到任务无法执行。在实现的过程中,我们比较了使用策略模式和不使用策略模式,得出了 使用策略模式可以增强程序扩展性 的结论。