java线程池工作原理和实现原理

为什么要使用线程池?

1.使用线程池可以复用池中的线程,不需要每次都创建新线程,减少创建和销毁线程的开销;

2.同时,线程池具有队列缓冲策略、拒绝机制和动态管理线程个数,特定的线程池还具有定时执行、周期执行功能,比较重要的一点是线程池可实现线程环境的隔离,例如分别定义支付功能相关线程池和优惠券功能相关线程池,当其中一个运行有问题时不会影响另一个。

如何构造一个线程池对象?

本文内容我们只聊线程池ThreadPoolExecutor,查看它的源码会发现它继承了AbstractExecutorService抽象类,而AbstractExecutorService实现了ExecutorService接口,ExecutorService继承了Executor接口,所以ThreadPoolExecutor间接实现了ExecutorService接口和Executor接口,它们的关系图如下。

我画了25张图展示线程池工作原理和实现原理,建议先收藏再阅读

一般我们使用的execute方法是在Executor接口中定义的,而submit方法是在ExecutorService接口中定义的,所以当我们创建一个Executor类型变量引用ThreadPoolExecutor对象实例时可以使用execute方法提交任务,当我们创建一个ExecutorService类型变量时可以使用submit方法,当然我们可以直接创建ThreadPoolExecutor类型变量使用execute方法或submit方法。

ThreadPoolExecutor定义了七大核心属性,这些属性是线程池实现的基石。

我画了25张图展示线程池工作原理和实现原理,建议先收藏再阅读

corePoolSize(int):核心线程数量。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到任务队列当中。线程池将长期保证这些线程处于存活状态,即使线程已经处于闲置状态。除非配置了allowCoreThreadTimeOut=true,核心线程数的线程也将不再保证长期存活于线程池内,在空闲时间超过keepAliveTime后被销毁。

workQueue:阻塞队列,存放等待执行的任务,线程从workQueue中取任务,若无任务将阻塞等待。当线程池中线程数量达到corePoolSize后,就会把新任务放到该队列当中。JDK提供了四个可直接使用的队列实现,分别是:基于数组的有界队列ArrayBlockingQueue、基于链表的无界队列LinkedBlockingQueue、只有一个元素的同步队列SynchronousQueue、优先级队列PriorityBlockingQueue。在实际使用时一定要设置队列长度。

maximumPoolSize(int):线程池内的最大线程数量,线程池内维护的线程不得超过该数量,大于核心线程数量小于最大线程数量的线程将在空闲时间超过keepAliveTime后被销毁。当阻塞队列存满后,将会创建新线程执行任务,线程的数量不会大于maximumPoolSize。

keepAliveTime(long):线程存活时间,若线程数超过了corePoolSize,线程闲置时间超过了存活时间,该线程将被销毁。除非配置了allowCoreThreadTimeOut=true,核心线程数的线程也将不再保证长期存活于线程池内,在空闲时间超过keepAliveTime后被销毁。

TimeUnit unit:线程存活时间的单位,例如TimeUnit.SECONDS表示秒。

RejectedExecutionHandler:拒绝策略,当任务队列存满并且线程池个数达到maximunPoolSize后采取的策略。ThreadPoolExecutor中提供了四种拒绝策略,分别是:抛RejectedExecutionException异常的AbortPolicy(如果不指定的默认策略)、使用调用者所在线程来运行任务CallerRunsPolicy、丢弃一个等待执行的任务,然后尝试执行当前任务DiscardOldestPolicy、不动声色的丢弃并且不抛异常DiscardPolicy。项目中如果为了更多的用户体验,可以自定义拒绝策略。

threadFactory:创建线程的工厂,虽说JDK提供了线程工厂的默认实现DefaultThreadFactory,但还是建议自定义实现最好,这样可以自定义线程创建的过程,例如线程分组、自定义线程名称等。

强烈建议通过使用ThreadPoolExecutor的构造方法创建,不要使用Executors

【强制】线程池不允许使用Executors创建,建议通过ThreadPoolExecutor的方式创建,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

Executors返回的线程池对象的弊端如下:

1.FixedThreadPool和SingleThreadPool:

允许的请求队列长度为Integet.MAX_VALUE,可能会堆积大量的请求从而导致OOM(内存溢出);

2.CachedThreadPool:

允许创建线程数量为Integet.MAX_VALUE,可能会创建大量的线程,从而导致OOM(内存溢出).

线程池的工作原理

 1.通过execute方法提交任务时,检查核心线程数有没有满,如果没有满直接创建一个线程,去执行任务(即使此时线程池中存在空闲线程)

我画了25张图展示线程池工作原理和实现原理,建议先收藏再阅读

 2.通过execute方法提交任务时,如果说核心线程数满了,就把这个任务加入到阻塞队列,等待线程池中线程调度执行

我画了25张图展示线程池工作原理和实现原理,建议先收藏再阅读

3.通过execute方法提交任务时,如果说阻塞队列也满了,就去检查是否达到最大线程数,如果没有达到最大线程数,这个时候去创建一个线程去执行任务 

我画了25张图展示线程池工作原理和实现原理,建议先收藏再阅读

4.当线程池中的线程执行完任务空闲时,会尝试从阻塞队列中取头结点任务执行。

我画了25张图展示线程池工作原理和实现原理,建议先收藏再阅读

5.通过execute方法提交任务,当线程池中线程数达到最大线程数,并且阻塞队列也存满时,新提交的任务由拒绝策略执行拒绝操作。 

我画了25张图展示线程池工作原理和实现原理,建议先收藏再阅读

 6.当线程中线程数超过核心线程数,并且未配置allowCoreThreadTimeOut = true,空闲时间超过线程存活时间的线程会被销毁(销毁空闲线程,不是销毁核心线程数中的线程),保持线程池中线程数为核心线程数

我画了25张图展示线程池工作原理和实现原理,建议先收藏再阅读

注意:上图表达的是销毁空闲线程,保持线程数为corePoolSize,不是销毁corePoolSize中的线程。

7.当设置allowCoreThreadTimeOut=true时,任何空闲时间超过线程存活时间的线程都会被销毁
 

我画了25张图展示线程池工作原理和实现原理,建议先收藏再阅读

 线程池底层实现原理

ThreadPoolExecutor的源码

我画了25张图展示线程池工作原理和实现原理,建议先收藏再阅读

 ctl用于表示线程池的状态和线程数,在ThreadPoolExecutor中使用32位二进制数来表示线程池的状态和线程池中线程数量,其中前3位表示线程池状态,后29位表示线程池中线程数。

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0))初始化线程池状态为RUNNING、线程池数量为0。

我画了25张图展示线程池工作原理和实现原理,建议先收藏再阅读

 COUNT_BITS值等于Integer.SIZE - 3,在源码中Integer.SIZE是32,所以COUNT_BITS=29。CAPACITY表示线程池允许的最大线程数,转算后的结果如下。

我画了25张图展示线程池工作原理和实现原理,建议先收藏再阅读

RUNNING、SHUTDOWN、STOP、TIDYING和TERMINATED分别表示线程池的不同状态,转算后的结果如下。

我画了25张图展示线程池工作原理和实现原理,建议先收藏再阅读

线程池处在不同的状态时,它的处理能力是不同的。 

我画了25张图展示线程池工作原理和实现原理,建议先收藏再阅读

线程池不同状态之间的转换时机及转换关系如下图。

我画了25张图展示线程池工作原理和实现原理,建议先收藏再阅读

一般,我们使用ThreadPoolExecutor的execute方法提交任务,execute源码如下 

execute源码对应的流程图 

我画了25张图展示线程池工作原理和实现原理,建议先收藏再阅读

 重点分析线程池中线程是如何执行任务、如何复用线程和线程空闲时间超限如何判断的。还是从execute方法入手,我们直接看它里面调用的addWorker方法,它实现了创建新线程执行任务

我画了25张图展示线程池工作原理和实现原理,建议先收藏再阅读

源码中将线程和任务封装到了Worker中,然后将Worker添加到HashSet集合中,添加成功后通过线程对象的start方法启动线程执行任务,既然这样那我们就来看看上图代码中的w = new Worker(firstTask)到底是如何执行的。 

我画了25张图展示线程池工作原理和实现原理,建议先收藏再阅读

Worker继承了AbstractQueuedSynchronizer,并且实现了Runnable接口,看到这里很清楚了任务最终由Worker中的run方法执行,而run方法里调用了runWorker方法,所以重点还是runWorker方法。 

我画了25张图展示线程池工作原理和实现原理,建议先收藏再阅读

 在runWorker方法中,使用循环,通过getTask方法不断从阻塞队列中获取任务执行,如果任务不为空则执行任务,这里实现了线程的复用,不断的获取任务执行,不用重新创建线程队列中获取的任务为null,则将Worker从HashSet集合中清除,注意这个清除就是空闲线程的回收。那getTask何时返回null?接着看getTask源码。

我画了25张图展示线程池工作原理和实现原理,建议先收藏再阅读

使用自定义线程工厂

为什么要这么做呢?是因为,当项目规模逐渐扩展,各系统中线程池也不断增多,当发生线程执行问题时,通过自定义线程工厂创建的线程设置有意义的线程名称可快速追踪异常原因,高效、快速的定位问题。

使用自定义拒绝策略

虽然,JDK给我们提供了一些默认的拒绝策略,但我们可以根据项目需求的需要,或者是用户体验的需要,定制拒绝策略,完成特殊需求。

线程池划分隔离

不同业务、执行效率不同的分不同线程池,避免因某些异常导致整个线程池利用率下降或直接不可用,进而影响整个系统或其它系统的正常运行。

4种拒绝策略

策略1:ThreadPoolExecutor.AbortPolicy()

抛出java.util.concurrent.RejectedExecutionException异常 ,示例如下:

private static class Worker implements Runnable {

    public void run() {

        System.out.println(Thread.currentThread().getName() + " is running");

    }

}

public static void main(String[] args) {

    int corePoolSize = 5;

    int maxPoolSize = 10;

    long keepAliveTime = 5;

    BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>(10);

     //拒绝策略1:将抛出 RejectedExecutionException.

    RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();

    ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maxPoolSize, 

keepAliveTime, TimeUnit.SECONDS, queue, handler);

    for(int i=0; i<100; i++) {

        executor.execute(new Worker());

    }

    executor.shutdown();

}

运行结果如下:

pool-1-thread-2 is running

pool-1-thread-3 is running

Exception in thread "main" java.util.concurrent.RejectedExecutionException

pool-1-thread-1 is running

pool-1-thread-7 is running

pool-1-thread-6 is running

pool-1-thread-4 is running

pool-1-thread-9 is running

pool-1-thread-8 is running

pool-1-thread-5 is running

at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:1760)

at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:767)

at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:658)

at concurrent.ThreadPoolDemo.main(ThreadPoolDemo.java:33)

pool-1-thread-10 is running

处理源码如下:

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {

    throw new RejectedExecutionException();

}

策略2:ThreadPoolExecutor.CallerRunsPolicy()

用于被拒绝任务的处理程序,它直接在 execute 方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务。如下:

RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy();

运行如下:

pool-1-thread-7 is running

pool-1-thread-7 is running

pool-1-thread-7 is running

pool-1-thread-7 is running

pool-1-thread-7 is running

pool-1-thread-7 is running

pool-1-thread-2 is running

pool-1-thread-3 is running

pool-1-thread-1 is running

pool-1-thread-8 is running

main is running

main is running

main is running

pool-1-thread-4 is running

pool-1-thread-7 is running

pool-1-thread-7 is running

pool-1-thread-7 is running

处理源码如下:

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {

            if (!e.isShutdown()) {

                r.run();

            }

}

策略3:ThreadPoolExecutor.DiscardOldestPolicy()

丢弃任务队列中最旧任务,然后尝试执行当前任务

RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardOldestPolicy();

这样运行结果就不会有100个线程全部被执行。处理源码如下:

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {

    if (!e.isShutdown()) {

        e.getQueue().poll();

        e.execute(r);

     }

}

策略4:ThreadPoolExecutor.DiscardPolicy()

用于被拒绝任务的处理程序,默认情况下它将丢弃被拒绝的任务。

运行结果也不会全部执行100个线程。

源码如下,实际就是对线程不执行操作:

public static class DiscardPolicy implements RejectedExecutionHandler {

        /**

         * Creates a <tt>DiscardPolicy</tt>.

         */

        public DiscardPolicy() { }



        /**

         * Does nothing, which has the effect of discarding task r.

         * @param r the runnable task requested to be executed

         * @param e the executor attempting to execute this task

         */

        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {

        }

}

这四种策略是独立无关的,是对任务拒绝处理的四中表现形式。最简单的方式就是直接丢弃任务。但是却有两种方式,到底是该丢弃哪一个任务,比如可以丢弃当前将要加入队列的任务本身(DiscardPolicy)或者丢弃任务队列中最旧任务(DiscardOldestPolicy)。丢弃最旧任务也不是简单的丢弃最旧的任务,而是有一些额外的处理。除了丢弃任务还可以直接抛出一个异常(RejectedExecutionException),这是比较简单的方式。抛出异常的方式(AbortPolicy)尽管实现方式比较简单,但是由于抛出一个RuntimeException,因此会中断调用者的处理过程。除了抛出异常以外还可以不进入线程池执行,在这种方式(CallerRunsPolicy)中任务将有调用者线程去执行。 


用于个人学习记录

转载自

http://p1-tt.byteimg.com/large/pgc-image/0898f2b175a0433791df4a37ce459c75?from=pc

ThreadPoolExecutor里面4种拒绝策略(详细)_long_here的专栏-CSDN博客

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值