自定义线程池 3.0

自定义线程池 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_0submit() 方法的源代码,增加一个分支,显然不可能。

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 版本,提供了拒绝策略,从而让使用者感知到任务无法执行。在实现的过程中,我们比较了使用策略模式和不使用策略模式,得出了 使用策略模式可以增强程序扩展性 的结论。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值