线程池
什么是线程池
一句话,线程的集合
线程池的好处
(摘录:https://blog.youkuaiyun.com/qq_40093255/article/details/116990431)
1.降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗;
2.提高系统响应速度,当有任务到达时,无需等待新线程的创建便能立即执行;
3.方便线程并发数的管控,线程若是无限制的创建,不仅会额外消耗大量系统资源,更是占用过多资源而阻塞系统或oom等状况,从而降低系统的稳定性。线程池能有效管控线程,统一分配、调优,提供资源使用率;
4.更强大的功能,线程池提供了定时、定期以及可控线程数等功能的线程池,使用方便简单。
线程池的创建
1.通过Executors创建
2.通过ThreadPoolExecutor 创建
具体有7种实现方法:
-
Executors.newFixedThreadPool:创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待。
Thread thread = new Thread(() -> System.out.println("线程" + Thread.currentThread().getName() + "执行")); //两种执行方式 threadPool.submit(thread); threadPool.execute(thread);
-
Executors.newCachedThreadPool:创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程。
ExecutorService threadPool = Executors.newCachedThreadPool(); for (int i = 0; i < 10; i++) { int finalI = i; threadPool.execute(() -> { System.out.println("(" + finalI + ")线程执行:" + Thread.currentThread().getName()); }); }
-
Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执行顺序。
//可以保证顺序执行 ExecutorService threadPool = Executors.newSingleThreadExecutor(); for (int i = 0; i < 10; i++) { int finalI = i; threadPool.execute(() -> { System.out.println(finalI + ":" + Thread.currentThread().getName() + "执行任务"); }); }
-
Executors.newScheduledThreadPool:创建一个可以执行延迟任务的线程池。
ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(4); System.out.println("任务启动时间:" + LocalDateTime.now()); //任务会延迟一秒执行 threadPool.schedule(() -> { System.out.println("任务执行时间:" + LocalDateTime.now()); }, 1, TimeUnit.SECONDS);
-
Executors.newSingleThreadScheduledExecutor:创建一个单线程的可以执行延迟任务的线程池。
ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); System.out.println("任务启动时间:" + LocalDateTime.now()); scheduledExecutorService.schedule(() -> { System.out.println("任务执行时间:" + LocalDateTime.now()); }, 1, TimeUnit.SECONDS);
-
Executors.newWorkStealingPool:创建一个抢占式执行的线程池(任务执行顺序不确定)【JDK 1.8 添加】
ExecutorService executorService = Executors.newWorkStealingPool(); for (int i = 0; i < 10; i++) { int finalI = i; executorService.execute(()->{ System.out.println(finalI +"被执行,线程名:"+Thread.currentThread().getName()); }); } //确保任务执行完 while (!executorService.isTerminated()){ }
-
ThreadPoolExecutor:手动创建线程池的方式,它创建时最多可以设置 7 个参数。
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 100, TimeUnit.SECONDS, new LinkedBlockingDeque<>(10)); // 执行任务 for (int i = 0; i < 10; i++) { int finalI = i; threadPoolExecutor.execute(() -> { System.out.println(finalI + " 被执行,线程名:" + Thread.currentThread().getName()); }); }
ThreadPoolExecutor 相比于其他创建线程池的优势在于,它可以通过参数来控制最大任务数和拒绝策略,让线程池的执行更加透明和可控
线程池参数
-
corePoolSize 核心线程数
核心线程数会一直存在,即使没有任务执行,也会创建线程直到到达核心线程数
-
queueCapacity 任务队列容量
没有闲置的核心线程时,会将任务加到任务队列中等待
-
maxPoolSize 最大线程数
线程池中允许的最大线程数量
-
keepAliveTime 线程空余时间
当线程空余时间达到设置的值,线程就会关闭,但不小于核心线程数
-
allowCoreThreadTimeout 允许核心线程超时
当值为true时,核心线程超时也会关闭
-
rejectedExecutionHandler 任务拒绝处理器
当线程数量到达最大线程数,且任务队列已满。执行拒绝策略
线程池默认的参数:
corePoolSize = 1
queueCapacity = Integer.MAX_VALUE
maxPoolSize = Integer.MAX_VALUE
keepAliveTime = 60秒
allowCoreThreadTimeout = false
rejectedExecutionHandler = AbortPolicy()
线程池的执行顺序
执行步骤:
1.创建线程池后
2.任务提交后,查看是否有核心线程
3.1 没有 -> 就创建核心线程 -> 执行任务 -> 执行完毕后又回到线程池中
3.2 有 -> 查看是否有闲置核心线程:
4.1 有 -> 执行任务 -> 执行完毕后又回到线程池
4.2 没有 -> 查看当前核心线程数是否核心线程数量:
5.1 否 -> 就创建核心线程 -> 执行任务 -> 执行完毕后又回到线程池中
5.2 是 -> 查看任务列表是否装载满:
6.1 没有 -> 就放入列表中,等待出现闲置线程
6.2 装满 -> 查看是否有普通线程(核心线程数到最大线程数量之间的线程)
7.1 没有 -> 就创建普通线程 -> 执行任务 -> 执行完毕后又回到线程池中
7.2 有 -> 查看是否有闲置普通线程
7.1.1 有 -> 执行任务 -> 执行完毕后又回到线程池中
7.1.2 没有 -> 查看现在所有线程数量是否为最大线程数:
8.1 是 -> 执行处理方案(默认处理抛出异常)
8.2 否 ->就创建普通线程-> 执行任务 -> 执行完毕后又回到线程池中
7.1.1 有 -> 执行任务 -> 执行完毕后又回到线程池中
7.1.2 没有 -> 查看现在所有线程数量是否为最大线程数:
8.1 是 -> 执行处理方案(默认处理抛出异常)
8.2 否 ->就创建普通线程-> 执行任务 -> 执行完毕后又回到线程池中