JUC-线程池

        池化技术的思想主要是为了减少每次获取资源的消耗,提高对资源的利用率。线程的创建和销毁都需要映射到操作系统,因此其代价是比较高昂的。出于避免频繁创建、销毁线程以及方便线程管理的需要,线程池应运而生。 

线程池的优势:

  1. 降低资源消耗:线程池通常会维护一些线程(数量为 corePoolSize),这些线程被重复使用来执行不同的任务,任务完成后不会销毁。在待处理任务量很大的时候,通过对线程资源的复用,避免了线程的频繁创建与销毁,从而降低了系统资源消耗。
  2. 提高响应速度:由于线程池维护了一批 alive状态的线程,当任务到达时,不需要再创建线程,而是直接由这些线程去执行任务,从而减少了任务的等待时间。
  3. 提高线程的可管理性:使用线程池可以对线程进行统一的分配,调优和监控。

 一、重要参数

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) 
参数名称说明
corePoolSize核心线程数。当线程数小于该值时,线程池会优先创建新线程来执行新任务。
maximumPoolSize线程池所能维护的最大线程数。当核心线程全部繁忙且任务队列打满之后,线程池会临时追加线程,直到总线程数达到maximumPoolSize这个上限。
keepAliveTime空闲线程的存活时间。当非核心线程处于空闲状态的时间超过这个时间后,该线程将被回收。将allowCoreThreadTimeOut参数设置为true后,核心线程也会被回收。
unitkeepAliveTime参数的时间单位。
workQueue任务队列,用于缓存未执行的任务。
threadFactory线程工厂。指定线程池创建线程的方式。
handler拒绝策略。当线程池和任务队列均处于饱和状态时,使用拒绝策略处理新任务。默认是 AbortPolicy,即直接抛出异常。

二、线程创建规则

  1. 线程数量小于 corePoolSize,直接创建新线程处理新的任务
  2. 线程数量大于等于 corePoolSize,workQueue 未满,则缓存新任务
  3. 线程数量大于等于 corePoolSize,但小于 maximumPoolSize,且 workQueue 已满。则创建新线程处理新任务
  4. 线程数量大于等于 maximumPoolSize,且 workQueue 已满,则使用拒绝策略处理新任务
条件做法
线程数 < corePoolSize创建新线程
线程数 ≥ corePoolSize,且 workQueue 未满缓存新任务
corePoolSize ≤ 线程数 < maximumPoolSize,且 workQueue 已满创建新线程
线程数 ≥ maximumPoolSize,且 workQueue 已满使用拒绝策略处理

三、阻塞队列

        通过 JDK 文档介绍,我们可知道有3中类型的容器可供使用,分别是同步队列有界队列无界队列。对于有优先级的任务,这里还可以增加优先级队列

实现类类型说明
SynchronousQueue同步队列这是一个内部没有任何容量的阻塞队列,任何一次插入操作的元素都要等待相对的删除/读取操作,否则进行插入操作的线程就要一直等待,反之亦然。
ArrayBlockingQueue有界队列基于数组的阻塞队列,按照 FIFO 原则对元素进行排序。使用无界队列后,当核心线程都繁忙时,后续任务可以无限加入队列,因此线程池中线程数不会超过核心线程数。这种队列可以提高线程池吞吐量,但代价是牺牲内存空间,甚至会导致内存溢出。另外,使用它时可以指定容量,这样它也就是一种有界队列了。
LinkedBlockingQueue无界队列基于链表的阻塞队列,按照 FIFO 原则对元素进行排序。在线程池初始化时,指定队列的容量,后续无法再调整。这种有界队列有利于防止资源耗尽,但可能更难调整和控制。
PriorityBlockingQueue优先级队列具有优先级的阻塞队列。存放在PriorityBlockingQueue中的元素必须实现Comparable接口,这样才能通过实现compareTo()方法进行排序。优先级最高的元素将始终排在队列的头部;PriorityBlockingQueue不会保证优先级一样的元素的排序,也不保证当前队列中除了优先级最高的元素以外的元素,随时处于正确排序的位置。

四、拒绝策略

线程数量大于等于 maximumPoolSize,且 workQueue 已满,则使用拒绝策略处理新任务。Java 线程池提供了4中拒绝策略实现类。

实现类说明
AbortPolicy(默认)丢弃新任务,并抛出 RejectedExecutionException。
DiscardPolicy不做任何操作,直接丢弃新任务。
DiscardOldestPolicy将当前处于等待队列列头的等待任务强行取出,然后再试图将当前被拒绝的任务提交到线程池执行。
CallerRunsPolicy直接运行这个任务的run方法,但并非是由线程池的线程处理,而是交由任务的调用线程处理。

五、源码解读

    // 存放线程池的运行状态 (runState) 和线程池内有效线程的数量 (workerCount)
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

    private static int workerCountOf(int c) {
        return c & CAPACITY;
    }

    private final BlockingQueue<Runnable> workQueue;

    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        /*
         * Proceed in 3 steps:
         *
         * 1. If fewer than corePoolSize threads are running, try to
         * start a new thread with the given command as its first
         * task.  The call to addWorker atomically checks runState and
         * workerCount, and so prevents false alarms that would add
         * threads when it shouldn't, by returning false.
         *
         * 2. If a task can be successfully queued, then we still need
         * to double-check whether we should have added a thread
         * (because existing ones died since last checking) or that
         * the pool shut down since entry into this method. So we
         * recheck state and if necessary roll back the enqueuing if
         * stopped, or start a new thread if there are none.
         *
         * 3. If we cannot queue task, then we try to add a new
         * thread.  If it fails, we know we are shut down or saturated
         * and so reject the task.
         */
        int c = ctl.get();
        // 步骤1:判断线程池当前线程数是否小于线程池大小
        if (workerCountOf(c) < corePoolSize) {
            // 增加一个工作线程并添加任务,成功则返回,否则进行步骤2
            // true代表使用coreSize作为边界约束,否则使用maximumPoolSize
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        // 步骤2:不满足workerCountOf(c) < corePoolSize或addWorker失败,进入步骤2
        // 校验线程池是否是Running状态且任务是否成功放入workQueue(阻塞队列)
        // 如果队列已满,则 offer 方法返回 false。否则,offer 返回 true
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            // 再次校验,如果线程池非Running且从任务队列中移除任务成功,则拒绝该任务
            if (! isRunning(recheck) && remove(command))
                reject(command);
            // 如果线程池工作线程数量为0,则新建一个空任务的线程
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        // 步骤3:如果线程池不是Running状态或任务入列失败,尝试扩容maxPoolSize后再次addWorker,失败则拒绝任务
        else if (!addWorker(command, false))
            reject(command);
    }

参考

Java 并发常见知识点&面试题总结(进阶篇) | JavaGuide

深入Java线程池:从设计思想到源码解读_云深i不知处的博客-CSDN博客_线程池的设计思路

Java 线程池原理分析 | 田小波的技术博客

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值