JUC(14)

68 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第14天,点击查看活动详情

8.1线程池

  1. 自定义线程池

运用了策略模式:通过封装接口面向对象的思想可以自定义拒绝策略

8.2 ThreadPoolExecutor

1.线程池状态

ThreadPoolExecutor 使用 int 的高 3 位来表示线程池状态,低 29 位表示线程数量

这些信息存储在一个原子变量 ctl 中,目的是将线程池状态与线程个数合二为一,这样就可以用一次 cas 原子操作 进行赋值

2.构造方法

  • corePoolSize 核心线程数目 (最多保留的线程数)
  • maximumPoolSize 最大线程数目
  • keepAliveTime 生存时间 - 针对救急线程
  • unit 时间单位 - 针对救急线程
  • workQueue 阻塞队列
  • threadFactory 线程工厂 - 可以为线程创建时起个好名字
  • handler 拒绝策略

救急线程:当当前需要执行任务数大于核心线程数 + 阻塞队列大小 时,将任务分发给救急线程进行处理,当救急线程全部派上用场却还是无法满足需求时就会执行拒绝策略

jdk提供了4种拒绝策略

  • AbortPolicy 让调用者抛出 RejectedExecutionException 异常,这是默认策略
  • CallerRunsPolicy 让调用者运行任务
  • DiscardPolicy 放弃本次任务
  • DiscardOldestPolicy 放弃队列中最早的任务,本任务取而代之

其他框架给出了额外的拒绝策略

  • Dubbo 的实现,在抛出 RejectedExecutionException 异常之前会记录日志,并 dump 线程栈信息,方 便定位问题
  • Netty 的实现,是创建一个新线程来执行任务
  • ActiveMQ 的实现,带超时等待(60s)尝试放入队列,类似我们之前自定义的拒绝策略
  • PinPoint 的实现,它使用了一个拒绝策略链,会逐一尝试策略链中每种拒绝策略

根据这个构造方法,JDK Executors 类中提供了众多工厂方法来创建各种用途的线程池

3.newFixedThreadPool(创建固定大小的线程池)

特点

  • 核心线程数 == 最大线程数(没有救急线程被创建),因此也无需超时时间
  • 阻塞队列是无界的,可以放任意数量的任务

评价 适用于任务量已知,相对耗时的任务

举个栗子

public static void main(String[] args) {
    ExecutorService pool = Executors.newFixedThreadPool(2, new ThreadFactory() {
        private AtomicInteger i = new AtomicInteger(1);
        @Override
        public Thread newThread(Runnable r) {
            return new Thread(r,"xksdspoll" + i.getAndIncrement());
        }
    });
    pool.execute(()->{
        System.out.println(Thread.currentThread().getName() + "1");
    });
    pool.execute(()->{
        System.out.println(Thread.currentThread().getName() +"2");
    });
    pool.execute(()->{
        System.out.println(Thread.currentThread().getName() +"3");
    });
}