【多线程】线程池解析

本文围绕Java线程池展开,介绍了线程池的工作原理,包括任务提交时不同线程数和队列状态下的处理策略,还提及了拒绝策略。阐述了使用线程池可减少开销、提高响应速度等好处。对ThreadPoolExecutor类进行了源码解析,涵盖继承实现、属性参数、构造方法、重要方法等,还对比了阻塞队列与普通队列。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题:什么是线程池?线程池的工作原理及使用线程池的好处?

一个线程池管理了一组工作线程,同时还包括了一个任务队列(阻塞队列),用于放置等待执行的任务队列。默认情况下,在创建了线程池以后,线程池中的线程数为0。

线程池工作原理:

当有任务提交,线程池的处理策略如下:

(1)如果此时的线程数小于核心线程数,即使线程池中的线程都处于空闲状态,也要创建新的线程来执行任务。(即每来一个任务,创建一个新线程)

(2)如果此时线程数大于核心线程数,并且未超过最大线程数。判断阻塞队列,阻塞队列未满的话,就将任务放入阻塞队列,等待有线程空闲了再取出执行。

(3)如果此时线程数大于核心线程数,并且未超过最大线程数。并且阻塞队列已满,这时判断线程数是否达到最大线程数。如果未达到最大线程数,则可以通过创建新的线程来完成该任务。

(4)若此时阻塞队列已满,而且线程数已达到最大线程数,那么就要通过RejectedExecutionHandler所指定的拒绝策略拒绝该任务。

*注:处于核心线程数和最大线程数之间的线程会被自动释放,当这写线程中某一线程的空闲时间超过keepAliveTime时,该线程就会被终止。该机制体现了线程池可以动态地调整线程池中的线程数量。

拒绝策略:

ThreadPoolExecutor的构造函数中制定了拒绝策略,即当任务数量超过系统实际承载能力时该如何处理。JDK中内置的四种拒绝策略:

(1)AbortPolicy策略——直接抛出异常,组织系统工作。

(2)CallerRunsPolicy策略——只要线程池未关闭,该策略直接在调用者线程中运行当前被丢弃的任务。不是真的丢弃任务,但是调用者线程的性能可能会急剧下降。

(3)DiscardOldestPolicy策略——丢弃最老的一个请求任务,即丢弃即将被执行的任务并尝试再次提交当前任务。

(4)DiscardPolicy策略——直接丢弃无法处理的任务,不做任何处理。

自定义拒绝策略:JDK并发之:线程池三(拒绝策略)_心灵圣地0_新浪博客

扩展RejectedExecutionHandler接口:

使用线程池的好处:

(1)通过重复利用已经创建好的线程,减少创建线程和线程销毁所花费的时间和系统资源开销。

(2)提高响应速度,当任务到达时,不需要创建新线程而立即执行。

(3)提高线程的可管理性,使用线程池可以对线程进行统一地分配和监控。

(4)如果不使用线程池,有可能造成系统创建大量线程,从而导致系统内存消耗完。

类和接口:

Executor接口、Executors类、ExecutorService接口、AbstractExecutorService抽象类、ThreadPoolExecutor类

线程池继承关系图:

Executor是一个顶层接口,只声明了一个方法,用来执行传进去的任务。

然后ExecutorService继承了Executor接口,并声明了一些方法。

抽象类AbstractExecutorService实现了ExecutorService接口,基本实现了ExecutorService接口中声明的所有方法。

然后ThreadPoolExecutor继承了AbstractExecutorService抽象类。

ThreadPoolExecutor源码解析:

(一)继承实现:

继承自AbstractExecutorService抽象类。

(二)基本属性及默认参数(7个)

(1)corePoolSize:核心线程数,当线程池中的线程数目达到核心线程数时,被提交的任务机会被存放到阻塞队列中

(2)maximumPoolSize:最大线程数,表示线程池中最多能够创建的线程数目。

(3)AliveTime:存活时间,作用于线程数处于核心线程数与最大线程数之间时,当核心线程数与最大线程数之间的某个线程的空闲时间超过该参数,该线程就会被终止。

(4)TimeUnit:AliveTime参数的时间单位。

(5)workQueue:一个阻塞队列,用于存放等待执行的任务。

(6)threadFactory:线程工厂,主要用于线程的创建。

(7)handler:拒绝策略,用于当最大线程数和阻塞队列都满的情况下提交任务的处理,默认取值为抛出异常。

(三)构造方法(4个)

核心构造方法:

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

(四)重要方法

(1)execute()

(2)submit()

(3)shutdown()

(4)shutdownNow()

问题:

(1)execute()和submit()的区别:

submit()有返回值,execute()没有返回值。应用时根据提交任务是否有返回值来选择对应方法。

submit()更加方便异常处理,如果任务可能会抛出异常,并且希望外面的调用者可以感知这些异常,就需要使用submit()方法,再通过Future.get抛出的异常。

(2)shutdown()和shutdownNow()的区别:

shutdown()后可以使用awaitTermination等待所有线程执行完当前任务。

shutdownNew()会迫使当前执行的所有任务停止工作。

(五)阻塞队列与普通队列

阻塞队列与普通队列的区别是当队列为空时从队列中取元素会被阻塞,当队列满时向队列中添加元素会被阻塞。

试图从空队列中获取元素时会被阻塞,直到其他线程向空队列插入了新的元素。

同样试图向满队列中添加元素也会被阻塞,直到其他线程使队列重新变得空闲(移除元素或者清空队列)。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值