1. 线程池的作用

  • 降低资源消耗:通过重用已经创建的线程来降低线程创建和销毁的消耗
  • 提高响应速度:任务到达时不需要等待线程创建就可以立即执行
  • 提高线程的可管理性:线程池可以统一管理、分配、调优和监控

2. 线程池的组成

  • 线程池管理器(ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务;
  • 2、工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;
  • 3、任务(Task):完成某个事件,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;
  • 4、任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。

3. 线程池的使用逻辑

4. 常用线程池的种类

  • Java通过Executors提供了四种线程池,这四种线程池都是直接或间接配置ThreadPoolExecutor的参数实现的。

    • newCachedThreadPool:用来创建一个可以无限扩大的线程池,适用于负载较轻的场景,执行短期异步任务。(可以使得任务快速得到执行,因为任务时间执行短,可以很快结束,也不会造成cpu过度切换)
    SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue。
    • 该线程池的工作机制是:
      1. 没有核心线程,直接向SynchronousQueue中提交任务
        1. 如果有空闲线程,就去取出任务执行;如果没有空闲线程,就新建一个
        1. 执行完任务的线程有60秒生存时间,如果在这个时间内可以接到新任务,就可以继续活下去,否则就拜拜
    • newFixedThreadPool:创建一个固定大小的线程池,因为采用无界的阻塞队列,所以实际线程数量永远不会变化,适用于负载较重的场景,对当前线程数量进行限制。(保证线程数可控,不会造成线程过多,导致系统负载更为严重)
    • newSingleThreadExecutor:创建一个单线程的线程池,适用于需要保证顺序执行各个任务。
    • newScheduledThreadPool:适用于执行延时或者周期性任务。

    4.1 ScheduledThreadPoolExecutor 添加任务提供了另外两个方法:

    scheduleAtFixedRate() :按某种速率周期执行
    scheduleWithFixedDelay():在某个延迟后执行
    • 该线程池的工作机制是:

        1. 调用上面两个方法添加一个任务
        1. 线程池中的线程从 DelayQueue 中取任务(time 大于等于当前时间的)
        1. 然后执行任务
        1. 执行完的任务修改time到指定时间,重新加入队列

- 5. 线程池的参数

  • corePoolSize:线程池中的核心线程数

    • 核心线程会一直存活,即使是闲置状态
    • CPU密集型的任务(加密、解密、压缩、复杂计算),一般core设置为CPU核数 + 1, 防止频繁的上下文切换;
    • IO密集型的任务(读写数据库、网络请求、文件读写),一般core设置为CPU核数 * 2
    • 具体要设置多少,还是测试、生产环境需要反复调试、修改
  • maximumPoolSize:线程池中最大线程数

    • 一般生产环境和core配的一致,防止线程池震荡
  • keepAliveTime:闲置超时时间

    • poolSize > corePoolSize 后创建的为闲置线程 - 如果服务波峰之后有较长时间的波谷,可以考虑尽快超时回收; 如果是密集的波峰,就要配久一点再超时
  • unit:keepAliveTime 超时时间的单位(时/分/秒等)
  • workQueue:线程池中的任务队列
  • threadFactory:为线程池提供创建新线程的线程工厂
  • rejectedExecutionHandler:线程池任务队列超过最大值之后的拒绝策略

    • java默认有4种实现, 如果不指定,默认选择Abort, 下面排列由最消极到最积极 - DiscardPolicy, 丢弃任务,但是不抛出异常

      • DiscardOldestPolicy 丢弃队列最前面的任务,然后提交当前Task到队列尾部

        • AbortPolicy, 丢弃任务并抛出RejectedExecutionException
      • CallerRunsPolicy 由调用线程处理该任务

- 6. 为什么要使用阻塞队列

  • 防止线程无限制的创建、浪费内存导致OOM or 浪费CPU频繁进行上下文切换
  • 阻塞队列可以保证任务队列中没有任务时阻塞获取任务的线程,使得线程进入wait状态,释放cpu资源。
  • 当队列中有任务时才唤醒对应线程从队列中取出消息进行执行。

参考:《2020最新Java基础精讲视频教程和学习路线!》
链接:https://juejin.cn/post/693865...


Java攻城师
451 声望390 粉丝

本人太过于丰富,无法简介