java自定义线程池(自定义线程池拒绝策略、自定义线程工厂设置线程名称)

193 阅读7分钟

记录一下自定义线程池相关的操作

自定义线程池(完整代码)


/**
 * 自定义线程池操作
 */
@Configuration
@Slf4j
public class BfThreadPool {
    // 核心线程容量
    private int corePoolSize = 10;
    // 最大容量
    private int maxPoolSize = 15;
    // 时间
    private long time = 60L;

    /**
     * 时间单位
     * 天(DAYS),
     * 小时(HOURS),
     * 分钟(MINUTES),
     * 秒(SECONDS),
     * 毫秒(MILLISECONDS),
     * 微秒(MICROSECONDS, 千分之一毫秒)
     * 毫微秒(NANOSECONDS, 千分之一微秒)
     *
     */
    private TimeUnit timeType = TimeUnit.MINUTES;

    /**
     * 任务队列
     * ①ArrayBlockingQueue
     * 数组型阻塞队列,基于数组的有界阻塞队列,按FIFO(先进先出策略)排序。
     * 使用一个重入锁(ReentrantLock),默认使用非公平锁,入队和出队共用一个锁,互斥。
     * 新任务进来后,会放到该队列的队尾,有界的数组可以防止资源耗尽问题。当线程池中线程数量达到corePoolSize后,再有新任务进来,则会将任务放入该队列的队尾,
     * 等待被调度。如果队列已经是满的,则创建一个新线程,如果线程数量已经达到maxPoolSize,则会执行拒绝策略。
     *
     * ②LinkedBlockingQuene
     * 链表型阻塞队列,基于链表的有界(近似无界)阻塞队列,默认初始化大小为Integer.MAX_VALUE(其实最大容量为Interger.MAX),按照FIFO先进先出策略)排序。
     * 使用一个重入锁(ReentrantLock),默认使用非公平锁,入队和出队共用一个锁,互斥。
     * 由于该队列的近似无界性,当线程池中线程数量达到corePoolSize后,再有新任务进来,会一直存入该队列,而不会去创建新线程直到maxPoolSize,因此使用该工作队列时,参数maxPoolSize其实是不起作用的。
     *
     * ③SynchronousQuene
     * 同步移交队列。容量为0,添加任务必须等待取出任务,这个队列相当于通道,不存储元素。
     * 一个不缓存任务的阻塞队列,生产者放入一个任务必须等到消费者取出这个任务。也就是说新任务进来时,不会缓存,而是直接被调度执行该任务,如果没有可用线程,
     * 则创建新线程,如果线程数量达到maxPoolSize,则执行拒绝策略。
     *
     * ④PriorityBlockingQueue
     * 优先级阻塞队列,具有优先级的无界阻塞队列,优先级通过参数Comparator实现。
     * 在 put 的时候会tryGrow,要说它有界也没问题,因为界是 Integer.MAX_VALUE,但其实上这个队列应该是无界的。默认采用元素自然顺序升序排列
     * (可以自定义Comparator)。
     * 使用一个重入锁分别控制元素的入队和出队。
     *
     * ⑤DelayQueue
     * 延时队列。无界,队列中的元素有过期时间,过期的元素才能被取出。使用一个重入锁分别控制元素的入队和出队,用 Condition 进行线程间的唤醒和等待。
     * 任务调度时候可以使用。
     */
    private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<Runnable>(50);

    /**
     * 拒绝策略
     * 当线程池线程数已满,并且工作队列达到限制,新提交的任务使用拒绝策略处理。可以自定义拒绝策略,拒绝策略需要实现 RejectedExecutionHandler 接口,
     * 有四种策略(1)抛异常、(2)丢弃任务不抛异常、(3)打回任务、(4)尝试与最老的线程竞争。
     * 默认拒绝策略是 AbortPolicy
     *
     * AbortPolicy:丢弃任务并抛出 RejectedExecutionException 异常
     * DiscardPolicy:丢弃任务,但是不抛出异常。可能导致无法发现系统的异常状态
     * DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务
     * CallerRunsPolicy:由调用线程处理该任务
     *
     * ①CallerRunsPolicy
     * 该策略下,在调用者线程中直接执行被拒绝任务的run方法,除非线程池已经shutdown,则直接抛弃任务。
     * 功能:当触发拒绝策略时,只要线程池没有关闭,就由提交任务的当前线程处理。
     * 使用场景:一般在不允许失败的、对性能要求不高、并发量较小的场景下使用,因为线程池一般情况下不会关闭,也就是提交的任务一定会被运行,
     * 但是由于是调用者线程自己执行的,当多次提交任务时,就会阻塞后续任务执行,性能和效率自然就慢了。
     *
     * ②AbortPolicy(线程池默认策略)
     * 该策略下,直接丢弃任务,并抛出RejectedExecutionException异常。
     * 功能:当触发拒绝策略时,直接抛出拒绝执行的异常,中止策略的意思也就是打断当前执行流程
     * 使用场景:这个就没有特殊的场景了,但是一点要正确处理抛出的异常。ThreadPoolExecutor中默认的策略就是AbortPolicy,
     * ExecutorService接口的系列ThreadPoolExecutor因为都没有显示的设置拒绝策略,所以默认的都是这个。但是请注意,
     * ExecutorService中的线程池实例队列都是无界的,也就是说把内存撑爆了都不会触发拒绝策略。当自己自定义线程池实例时,
     * 使用这个策略一定要处理好触发策略时抛的异常,因为他会打断当前的执行流程。
     * java.util.concurrent.RejectedExecutionException
     *
     * ③DiscardPolicy
     * 该策略下,直接丢弃任务,什么都不做。
     * 功能:直接静悄悄的丢弃这个任务,不触发任何动作
     * 使用场景:如果你提交的任务无关紧要,你就可以使用它 。因为它就是个空实现,会悄无声息的吞噬你的的任务。所以这个策略基本上不用了
     *
     * ④DiscardOldestPolicy
     * 该策略下,抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列
     * 功能:如果线程池未关闭,就弹出队列头部的元素,然后尝试执行
     * 使用场景:这个策略还是会丢弃任务,丢弃时也是毫无声息,但是特点是丢弃的是老的未执行的任务,而且是待执行优先级较高的任务。基于这个特性,我能想到的场景就是,
     * 发布消息,和修改消息,当消息发布出去后,还未执行,此时更新的消息又来了,这个时候未执行的消息的版本比现在提交的消息版本要低就可以被丢弃了。
     * 因为队列中还有可能存在消息版本更低的消息会排队执行,所以在真正处理消息的时候一定要做好消息的版本比较。
     *
     * 当然也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策略。如记录日志或持久化不能处理的任务。
     * public interface RejectedExecutionHandler {
     *     void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
     * }
     */
    private RejectedExecutionHandler refusalStrategy = new ThreadPoolExecutor.AbortPolicy();

    // 自定义的线程池任务拒绝策略
    private RejectedExecutionHandler rejected = (Runnable r, ThreadPoolExecutor executor) -> {
//        log.info("线程任务被拒绝 : " + r);
        try {
            // 等待,尝试将当前被拒绝的任务重新加入线程队列
            int i = RandomUtil.randomInt(500, 1000);
            // 此时主线程是会被阻塞的
            Thread.sleep(i);
//            log.info("尝试重新加入 : " + r);
            executor.execute(r);
        } catch (Exception e) {
            e.printStackTrace();
        }
    };


    /**
     * 自定义扩容线程池
     *
     * @return
     */
    @Bean(name = "BfThreadPool")
    public ThreadPoolExecutor threadPoolTaskExecutor() {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                corePoolSize, maxPoolSize,
                time, timeType,
                queue, new NamedThreadFactory("Bf的线程池"), rejected);
        return threadPoolExecutor;
    }

自定义线程工厂

public class NamedThreadFactory implements ThreadFactory {
    private static final AtomicInteger poolNumber = new AtomicInteger(1);
    private final ThreadGroup group;
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private final String namePrefix;

    NamedThreadFactory(String name) {
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
        if (null == name || name.isEmpty()) {
            name = "pool";
        }
        namePrefix = name + "-thread-";
    }

    public Thread newThread(Runnable r) {
        Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
        if (t.isDaemon())
            t.setDaemon(false);
        if (t.getPriority() != Thread.NORM_PRIORITY)
            t.setPriority(Thread.NORM_PRIORITY);
        return t;
    }
}

自定义拒绝策略

 // 自定义的线程池任务拒绝策略
    private RejectedExecutionHandler rejected = (Runnable r, ThreadPoolExecutor executor) -> {
//        log.info("线程任务被拒绝 : " + r);
        try {
            // 等待,尝试将当前被拒绝的任务重新加入线程队列
            int i = RandomUtil.randomInt(500, 1000);
            // 此时主线程是会被阻塞的
            Thread.sleep(i);
//            log.info("尝试重新加入 : " + r);
            executor.execute(r);
        } catch (Exception e) {
            e.printStackTrace();
        }
    };

感谢

# 万字详解 java 线程池,让我们的高并发程序更稳健

# Java自定义线程池拒绝策略,实现任务被拒绝后重新加入线程池,控制线程池中任务溢出风险