为什么

有了多线程,为什么还要用线程池?

  1. 提高响应速度;
  2. 降低资源消耗;
  3. 进行线程管理;

    如何创建线程池

    jdk提供了ExecutorService接口表示线程池

  4. 使用juc的工具类Executors类,可以创建4种线程池:

  • newFixedThreadPool
  • newSingleThreadExecutor
  • newCachedThreadPool
  • newScheduledThreadPool

但是,《阿里巴巴 Java 开发手册》中强制线程池不允许使用 Executors 去创建,封装太好,让人少了思考。不过它里边还是利用 ThreadPoolExecutor 类实现的。

Executors 返回线程池对象的弊端如下: FixedThreadPool 和 SingleThreadExecutor : 允许请求的队列⻓度为 Integer.MAX_VALUE ,可能堆积大量的请求,从而导致 OOM。 CachedThreadPool 和 ScheduledThreadPool : 允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致 OOM。

  1. 使用构造方法ThreadPoolExecutor 的方式创建,提倡这种方式,因为可以更清楚的理解线程池的运行规则。

    ThreadPoolExecutor 类分析

    ThreadPoolExecutor提供了四个构造方法,我们看的是参数最多的这个,其余3个都是在这个构造方法的基础上产生的(给定其他默认的构造参数)。
    1. public ThreadPoolExecutor(int corePoolSize,
    2. int maximumPoolSize,
    3. long keepAliveTime,
    4. TimeUnit unit,
    5. BlockingQueue<Runnable> workQueue,
    6. ThreadFactory threadFactory,
    7. RejectedExecutionHandler handler) {
    8. if (corePoolSize < 0 ||
    9. maximumPoolSize <= 0 ||
    10. maximumPoolSize < corePoolSize ||
    11. keepAliveTime < 0)
    12. throw new IllegalArgumentException();
    13. if (workQueue == null || threadFactory == null || handler == null)
    14. throw new NullPointerException();
    15. this.acc = System.getSecurityManager() == null ?
    16. null :
    17. AccessController.getContext();
    18. this.corePoolSize = corePoolSize;
    19. this.maximumPoolSize = maximumPoolSize;
    20. this.workQueue = workQueue;
    21. this.keepAliveTime = unit.toNanos(keepAliveTime);
    22. this.threadFactory = threadFactory;
    23. this.handler = handler;
    24. }

    参数分析

  • corePoolSize:核心线程个数
  • maximumPoolSize:最大线程个数
  • workQueue:当新任务来的时候,如果线程池当前运行的线程个数已达到核心线程个数,那这个新任务会放到队列中。
  • keepAliveTime:当线程池中的线程数量大于 corePoolSize 的时候,如果这时没有新的任务 提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了keepAliveTime 才会被回收销毁。
  • unit:keepAliveTime 参数的时间单位。
  • threadFactory:executor 创建新线程的时候会用到。
  • handler :拒绝策略。 ```java public static void main(String[] args) { ExecutorService threadPool = new ThreadPoolExecutor(2,

    1. 5,
    2. 10L,
    3. TimeUnit.SECONDS,
    4. new LinkedBlockingQueue<>(1),
    5. Executors.defaultThreadFactory(),
    6. new ThreadPoolExecutor.DiscardPolicy());

    // 对象创建后,线程实际还没开始创建 // 执行execute时,检查当前池中线程数大小是否小于core number, 如果是,则创建新线程 threadPool.execute(()->{

    1. System.out.println("任务1@" + Thread.currentThread().getName());
    2. sleepTime();
    3. System.out.println(1);

    }); //检查当前池中线程数大小是否小于core number, 如果是,则创建新线程 threadPool.execute(() -> {

      System.out.println("任务2@" + Thread.currentThread().getName());
      sleepTime();
      System.out.println(2);
    

    });

    // 检查当前池中线程数大小是否小于core number, 如果不是,则偿试放入队列 // 这个任务是加到队列去的, 注意队列大小只有1, // 队列中的任务是什么时候取出来的? 任务1或者2结束后所占用的线程 会运行队列中的任务,这个任务是在最后才运行,比4运行的还晚 threadPool.execute(() -> {

      System.out.println("任务3@" + Thread.currentThread().getName());
      sleepTime();
      System.out.println(3);
    

    });

// 检查当前池中线程数大小是否小于core number, 如果不是,则偿试放入队列,放入队列也失败,则增加新的worker线程
// 这个任务是加到core以外的新线程去的
threadPool.execute(() -> {
    System.out.println("任务4@" + Thread.currentThread().getName());
    sleepTime();
    System.out.println(4);
});

}

```

拒绝策略

  • ThreadPoolExecutor.AbortPolicy :抛出 RejectedExecutionException 来拒绝新任务的处理。
  • ThreadPoolExecutor.CallerRunsPolicy :交给调用方来自行处理。
  • ThreadPoolExecutor.DiscardPolicy : 不处理新任务,直接丢弃掉。
  • ThreadPoolExecutor.DiscardOldestPolicy : 此策略将丢弃最早的未处理的任务请求。

在默认情况下, ThreadPoolExecutor的拒绝策略是AbortPolicy,抛出RejectedExecutionException 来拒绝新来的任务。

如何关闭线程池

方法就是这两个:shutdown()/shutdownNow()。

  • shutdown() 执行后停止接受新任务,会把队列的任务执行完毕。
  • shutdownNow() 也是停止接受新任务,但会中断所有的任务,将线程池状态变为 stop。