1. 为什么用线程池:

归纳为三点:
避免了频繁的创建/销毁线程,减少系统开销,提高响应速度。
提供响应速度, 有新任务需要执行的时候, 不需要等待线程创建就可以马上执行。
合理的设置线程池大小可以避免因为线程数超过硬件资源带来的问题。

2. 线程池使用:

线程池几基本架构如下:
image.png
Executor是一个顶级接口, 只包含了execute方法, 用来执行一个runnable任务.
ExecutorService继承了Executor,定义了更多的线程池相关的操作。
ThreadPoolExecutor是ExecutorService接口的实现(基础线程池的核心功能), 实现了线程池工作的完整机制。
ScheduledThreadPoolExecutor 继承 ThreadPoolExecutor, 能执行周期性任务或者定时任务的线程池。

2.1 Executors工厂类创建线程

  1. 固定大小的线程池:

newFixedThreadPool**(int nThreads)
newFixedThreadPool(int nThreads, ThreadFactory threadFactory)**
创建一个包含nThreads个工作线程的线程池, 这n个工作线程共享一个无界队列(不设置大小的队列). 当新任务提交线程池时, 如果没有空闲线程, 那么任务就放到队列中等待. 直到有空闲线程来处理队列任务。缺点: 队列无限大, 会存在OOM.

  1. 单线程的线程池

newSingleThreadExecutor**()
newSingleThreadExecutor**(ThreadFactory threadFactory)
创建只包含一个工作线程的线程池, 类似newFixedThreadPool(1), 不同的是, 如果这个线程意外终止, 线程池会创建一个新的线程继续执行。 缺点: 队列无限大, 会存在OOM.

  1. 可缓存线程的线程池

newCachedThreadPool()
newCachedThreadPool(ThreadFactory threadFactory)
当新任务提交到线程池时候,如果线程池中有空闲线程, 则利用空闲线程来运行任务, 否则创建一个线程运行任务, 并将该线程添加到线程池中. 此外, 线程池会终止并移除超过60秒未使用的空闲线程。 有限的避免了线程的创建/销毁的开销。 缺点: 可以无限制创建线程, 容易出现OOM.

2.2 构造函数创建线程

  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:即线程池的最大线程数量
keepAliveTime和unit:超出核心线程数后的存活时间和单位
workQueue:是一个阻塞的队列,用来保存线程池要执行的所有任务。通常可以取下面三种类型:

  1. 1ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;
  2. 2LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;
  3. 3SynchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。

ThreadFactory: 创建线程的工厂类, 一般用Executors.defaultThreadFactory()默认工厂.
handler:当队列和最大线程池都满了之后的饱和策略。饱和策略如下:

  1. 1AbortPolicy:直接抛出异常,默认策略;
  2. 2CallerRunsPolicy:用调用者所在的线程来执行任务;
  3. 3DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
  4. 4DiscardPolicy:直接丢弃任务;
  5. 当然也可以根据应用场景实现 RejectedExecutionHandler 接口,自定义饱和策略,如记录
  6. 日志或持久化存储不能处理的任务

如下是一个自定义拒绝策略的例子:

  1. public class MyPolicy implements RejectedExecutionHandler {
  2. private static final MyPolicy policy = new MyPolicy();
  3. private MyPolicy() {
  4. }
  5. public static MyPolicy getInstance() {
  6. return policy;
  7. }
  8. @Override
  9. public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
  10. System.out.println("Task " + r.toString() + " rejected from " + executor.toString());
  11. }
  12. public static void main(String[] args) {
  13. ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 3, 3L,
  14. TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(5), Executors.defaultThreadFactory(), MyPolicy.getInstance());
  15. for (int i = 0; i < 10; i++) {
  16. executor.execute(() -> {
  17. try {
  18. TimeUnit.MILLISECONDS.sleep(10);
  19. System.out.println(Thread.currentThread().getName() + " is running");
  20. } catch (InterruptedException e) {
  21. e.printStackTrace();
  22. }
  23. });
  24. }
  25. try {
  26. TimeUnit.SECONDS.sleep(1);
  27. executor.shutdownNow();
  28. System.out.println("ending...");
  29. } catch (InterruptedException e) {
  30. e.printStackTrace();
  31. }
  32. }
  33. }
  34. ------------------------
  35. Task com.halooing.chapter6.MyPolicy$$Lambda$1/381259350@6acbcfc0 rejected from java.util.concurrent.ThreadPoolExecutor@5f184fc6[Running, pool size = 3, active threads = 3, queued tasks = 5, completed tasks = 0]
  36. Task com.halooing.chapter6.MyPolicy$$Lambda$1/381259350@6acbcfc0 rejected from java.util.concurrent.ThreadPoolExecutor@5f184fc6[Running, pool size = 3, active threads = 3, queued tasks = 5, completed tasks = 0]
  37. pool-1-thread-1 is running
  38. pool-1-thread-3 is running
  39. pool-1-thread-2 is running
  40. pool-1-thread-3 is running
  41. pool-1-thread-1 is running
  42. pool-1-thread-2 is running
  43. pool-1-thread-3 is running
  44. pool-1-thread-1 is running
  45. ending...

特别重要: 分析上述例子, 不同情况下, 线程池有多少线程: 上述例子可知, 核心线程:2,最大线程:3, 队列长度是5.
情况1: 当核心线程未全部使用的时候(当前线程数 < 核心线程数)。线程池线程数量是当前线程数。
情况2: 当核心线程全部使用, 队列未满的时候。线程池线程数量是核心线程数。
情况3: 当核心线程全部使用, 队列已满, 并且当前线程数小于最大线程数的时候。线程池线程数量是当前线程数。
情况4: 当核心线程全部使用, 队列已满, 并且当前线程数等于最大线程数的时候。线程池线程数量是最大线程数。
情况5: 当前线程数大于最大线程数的时候, 采用拒绝策略. 上述案例中, 队列可以放5个任务, 最大线程数是3, 开启了10个任务, 所以有两个是拒绝掉, 是正确的。

3. 线程池一些源码分析

4. 自定义线程池实现