1. 对于数组中的工具类是Arrays
  2. 对于集合中的工具类是Collections
  3. 对于线程池中的工具类是Executors—->线程池的工厂
  4. Executors可以看成是线程池的一个工厂,用来产生各种各样的线程池
  5. 线程池默认的拒绝策略:在任务不能再提交的时候,抛出异常,及时反馈程序运行状态—->AbortPolicy

SingleThreadPool

  1. 继承自AbstractExecutorService类
  2. SingleThreadPool中只有一个线程,可以保证扔进去的线程是顺序执行的
  3. 可以保证线程执行的顺序
  4. 为什么要有单线程的线程池(自己new一个Thread不就行了吗?)
    1. 线程池有任务队列,假如自己new Thread,还要自己构造一个任务队列,自己去维护,加大了工作量
    2. 单线程的线程池有完整的生命周期管理,自己new还得自己慢慢new
    3. 线程池将Thread进行了封装,用起来更方便
  5. 源码是如何new该线程池的?
    1. 在new的过程中,先new了一个线程池对象,然后给这个线程池对象加了一层包装
    2. 包装的作用见下:帮助进行垃圾回收的;为了将第一个线程的线程池的生命周期定义死
  6. 最终都是ThreadPoolExecutor来实现
  7. 这个谈不上keepAliveTime,因为他永远不会消失,核心线程默认的情况下不会被回收掉,所以这个参数和下面那个参数没有实际意义
  8. ❓这个线程池,可以用单线程向里面放任务????
  9. 阿里手册是不建议用jdk**自带的实现**来定义线程池的
    1. 可以自定义线程的名称
    2. 因为阻塞会太多(阻塞队列以Integer.MaxValue为上界),阻塞的任务越积越多,很有可能会OOM
    3. 因为执行中的线程会太多
    4. 假如到了上界后,可能会使用jdk默认的拒绝策略,但是**那些默认的拒绝策略一般不会在生产环境中使用**,一般要自定义拒绝策略
    5. 像阿里、京东这种体量的公司,任务量极有可能会达到这个最大值(不能说一定,是非常有可能的,秒杀来的时候一堆的对象拥过来,一个线程处理不过来,就会堆过来,越堆越多,会达到Integer的最大值,是会满的)

image.png

  1. cs
  1. package com.mashibing.juc.c_026_01_ThreadPool;
  2. import java.util.concurrent.ExecutorService;
  3. import java.util.concurrent.Executors;
  4. public class T07_SingleThreadPool {
  5. public static void main(String[] args) {
  6. ExecutorService service = Executors.newSingleThreadExecutor();
  7. for(int i=0; i<5; i++) {
  8. final int j = i;
  9. service.execute(()->{
  10. System.out.println(j + " " + Thread.currentThread().getName());
  11. });
  12. }
  13. }
  14. }

CachedPool

  1. 特点:来一个任务,就给他起一个线程;当线程池中有线程存在的时候,还有没有到达60s的回收时间的线程存在,即有线程存在的时候,就利用现有的线程;但是当新任务来的时候,所有线程都正在忙着,就起一个新的,不等待。直到上界!
  2. 因为阻塞队列是SynchronousQueue() ;
  3. 来一个新的任务必须马上执行,没有线程空着,就new一个新的线程
  4. 阿里不建议使用该线程池,因为线程数可能起的会非常非常多,线程数基本接近于没有上界
  5. 一旦线程达到这个级别,cpu会浪费大量时间在线程之间的切换上面,会造成效率非常低
  6. cpu基本都花在线程之间的切换上,不干别的了(cpu、内存飙升—->jstack查看jvm内部运行信息)
  7. 一般不会用这样的线程池,当你可以肯定线程不会堆积,不会起太多线程的时候可以用这个线程池(出问题—->领导谈话

image.png

  1. dd
  1. package com.mashibing.juc.c_026_01_ThreadPool;
  2. import java.util.concurrent.ExecutorService;
  3. import java.util.concurrent.Executors;
  4. import java.util.concurrent.TimeUnit;
  5. public class T08_CachedPool {
  6. public static void main(String[] args) throws InterruptedException {
  7. ExecutorService service = Executors.newCachedThreadPool();
  8. System.out.println(service);
  9. for (int i = 0; i < 2; i++) {
  10. service.execute(() -> {
  11. try {
  12. TimeUnit.MILLISECONDS.sleep(500);
  13. } catch (InterruptedException e) {
  14. e.printStackTrace();
  15. }
  16. System.out.println(Thread.currentThread().getName());
  17. });
  18. }
  19. System.out.println(service);
  20. TimeUnit.SECONDS.sleep(80);
  21. System.out.println(service);
  22. }
  23. }

FixedThreadPool

  1. 指定一个参数,表示到底有多少个线程
  2. 核心线程和最大线程都是固定的,那么线程就全都是核心线程,故也没有回收之说

image.png

  1. 在阿里工作,看到LinkedBlockingQueue要小心,这是他不建议用的,因为会堆积太多,造成OOM的异常,或者超过阻塞队列的最大容量会使用你不希望使用的拒绝策略


什么时候用CachedThreadPool?什么时候用FixedThreadPool?

  1. 线程数量问题,要预估并发量,到底线程池中起多少个线程?

image.png

  1. 很难精准定义一个任务应该用多少线程,一般情况下很多人是用一个经验值设好进行压测,在高并发的场景中,压测是少不了的—->测试专业:性能测试(压力测试)、测试开发(测开)
  2. 测试团队会模拟各种各样的高并发场景对程序进行压测,如果达不到预期的需求,再调整之前设置的线程数
  3. 公式很难计算,运用到实际中去是很难的,知道公式也很难去运用!
  4. 可以用公式预估,然后用预估结果作为初值,**进行压测**!!!
  5. 根据压测结果进行调整!!!
  6. 线程是要占用cpu资源的,当某一个线程执行的时候发生了等待,这时候别的线程就可以占用cpu了,线程之间竞争抢占cpu
  7. 就算用了这个公式,在实战上线之前依然需要进行强力的压力测试,达不到要求还得调!!!
  8. 有人说实际中好像用的都是cpu核数,cpu用到100%,基本是cpu密集型的,没有io密集型的

Cached vs. Fixed

  1. 假如任务来的时候忽高忽低,不平稳,但是我要保证来了就要有人做这个事,就可以用Cached
  2. 保证任务不堆积,用Cached
  3. 假如任务来的比较平稳,大概都是这个量,不断的量,可以估算出一个值(比如8),完全可以处理,就可以new8个线程扔在这就行了
  4. 阿里是两个都不用,自己进行估算(多少个线程能够进行处理),估算好了进行精确的定义

ScheduledThreadPoolExecutor

  1. 里面的任务都是隔多长时间之后可以执行的任务
  2. DelayedWorkQueue();
  3. 这个线程池用得不多
  4. 简单的用jdk里面的Timer
  5. 复杂的用定时器框架
  6. 定时任务线程池
  7. 定时器框架:quartz、cron(涉及到shell脚本,写起来可能会费劲,但很强大)
  8. 这是一个专门给定时任务用的线程池
  9. 有个很好用的方法:隔多长时间执行一次任务service.scheduleAtFixedRate(()->{});可以灵活地对时间进行控制
  10. 因为MaximumPoolSize依然为Integer的最大值,所以在阿里依旧不能用!!!

image.png
image.png

  1. package com.mashibing.juc.c_026_01_ThreadPool;
  2. import java.util.Random;
  3. import java.util.concurrent.Executors;
  4. import java.util.concurrent.ScheduledExecutorService;
  5. import java.util.concurrent.TimeUnit;
  6. public class T10_ScheduledPool {
  7. public static void main(String[] args) {
  8. ScheduledExecutorService service = Executors.newScheduledThreadPool(4);
  9. service.scheduleAtFixedRate(()->{
  10. try {
  11. TimeUnit.MILLISECONDS.sleep(new Random().nextInt(1000));
  12. } catch (InterruptedException e) {
  13. e.printStackTrace();
  14. }
  15. System.out.println(Thread.currentThread().getName());
  16. }, 0, 500, TimeUnit.MILLISECONDS);
  17. }
  18. }

面试题:假如提供一个闹钟服务,订阅这个服务的人特别多,有10亿人,该怎么优化?

(阿里)

  1. 开放题,没有标准答案
  2. 一鸣的亿级流量的前置知识
  3. cg?nginx
  4. 思想就是把定时任务分发到边缘的服务器上去(一台不够的话)
  5. 每一台服务器上每天的定时任务很多,在一台上的优化—->一个队列存着这些任务,一堆的线程去消费,也是要用线程池的
  6. 大的结构上要用分而治之的思想,主服务器同步到边缘服务器在每一台服务器上用线程池+任务队列

总结:上面的四种线程池全部用的是ThreadPoolExecutor