1 线程池的概念

1.1 创建和销毁对象是非常耗费时间的

• 创建对象:需要分配内存等资源
• 销毁对象:虽然不需要程序员操心,但是垃圾回收器会在后台一直跟踪并销毁
• 对于经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
• 思路:创建好多个线程,放入线程池

1.2 线程池的好处

• 提高响应速度(减少了创建新线程的时间)
• 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
• 提高线程的可管理性:避免线程无限制创建、从而销耗系统资源,降低系统稳定性,甚至内存溢出或者CPU耗尽

1.3 线程池常用的类及方法

Executor:线程池顶级接口,只有一个方法

ExecutorService:真正的线程池接口
• void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行Runnable
Future submit(Callable task):执行任务,有返回值,一般又来执行Callable
• void shutdown() :关闭连接池

AbstractExecutorService:基本实现了ExecutorService的所有方法

ThreadPoolExecutor:默认的线程池实现类

ScheduledThreadPoolExecutor:实现周期性任务调度的线程池

Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
• Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
• Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
• Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
• Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
阿里规约:线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
1、通过ThreadPoolExecutor的方式创建线程池,让更加明确线程池的运行规则,规避资源耗尽的风险。
2、通过实现ThreadFactory,我们可以自定义有意义的线程名字,方便在出现问题的时候进行排查(特别是生产问题)。
3、通过实现RejectedExecutionHandler,我们可以做一些日志记录,有利于分析线程池的运行状况。

1.4 线程池参数

corePoolSize:核心池的大小
• 默认情况下,创建了线程池后,线程数为0,当有任务来之后,就会创建一个线程去执行任务。
• 但是当线程池中线程数量达到corePoolSize,就会把到达的任务放到队列中等待。
maximumPoolSize:最大线程数。
• corePoolSize和maximumPoolSize之间的线程数会自动释放,小于等于corePoolSize的不会释放。当大于了这个值就会将任务由一个丢弃处理机制来处理。
keepAliveTime:线程没有任务时最多保持多长时间后会终止
• 默认只限于corePoolSize和maximumPoolSize之间的线程
TimeUnit
• keepAliveTime的时间单位
BlockingQueue
• 存储等待执行的任务的阻塞队列,有多中选择,可以是顺序队列、链式队列等。
ThreadFactory
• 线程工厂,默认是DefaultThreadFactory,Executors的静态内部类
RejectedExecutionHandler
• 拒绝处理任务时的策略。如果线程池的线程已经饱和,并且任务队列也已满,对新的任
务应该采取什么策略。
• 比如抛出异常、直接舍弃、丢弃队列中最旧任务等,默认是直接抛出异常。
• 1、CallerRunsPolicy:如果发现线程池还在运行,就直接运行这个线程
• 2、DiscardOldestPolicy:在线程池的等待队列中,将头取出一个抛弃,然后将当前线程放进去。
• 3、DiscardPolicy:什么也不做
• 4、AbortPolicy:java默认,抛出一个异常

1.5 创建一个线程池

  1. public class ThreadPoolTest {
  2. public static void main(String[] args) {
  3. ThreadPoolTest test = new ThreadPoolTest();
  4. test.threadpoolCreat();
  5. }
  6. public void threadpoolCreat() {
  7. ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
  8. .setNameFormat("demo-pool-%d").build();
  9. ExecutorService threadPool = new ThreadPoolExecutor(4,
  10. 5,
  11. 1000L,
  12. TimeUnit.MILLISECONDS,
  13. // new ArrayBlockingQueue<Runnable>(5),//有界队列
  14. new LinkedBlockingDeque<>(),//无界队列
  15. namedThreadFactory,
  16. new ThreadPoolExecutor.AbortPolicy());
  17. // threadPool.execute(() -> System.out.println(Thread.currentThread().getName()));
  18. for (int i = 0; i < 11; i++) {
  19. threadPool.execute(new ThreadTest());
  20. new Thread(new ThreadTest()).start();
  21. }
  22. threadPool.shutdown();
  23. }
  24. }
  25. class ThreadTest implements Runnable {
  26. @Override
  27. public void run() {
  28. //todo
  29. System.out.println(Thread.currentThread().getName());
  30. }
  31. }

输出:

  1. demo-pool-0
  2. demo-pool-0
  3. demo-pool-0
  4. demo-pool-0
  5. demo-pool-0
  6. demo-pool-0
  7. demo-pool-0
  8. demo-pool-0
  9. Thread-0
  10. demo-pool-1
  11. demo-pool-2
  12. Thread-1
  13. Thread-2
  14. Thread-3
  15. Thread-5
  16. Thread-6
  17. Thread-8
  18. Thread-9
  19. Thread-10
  20. demo-pool-3
  21. Thread-7
  22. Thread-4

可以看到线程被多次利用,这也体现了线程池最大的特性。

ThreadFactoryBuilder来自于Google开发的Guava包。假如你使用的是maven,你必须在pom.xml中导入Guava依赖:

  1. <dependency>
  2. <groupId>com.google.guava</groupId>
  3. <artifactId>guava</artifactId>
  4. <version>28.1-jre</version>
  5. </dependency>