深入理解threadpoolexecutor第二弹(代码片段)

风在哪 风在哪     2022-12-10     265

关键词:

从源头解析ThreadPoolExecutor第二弹—ThreadPoolExecutor的内部类

ThreadPoolExecutor主要包括如下内部类:

其中AbortPolicy、CallerRunsPolicy、DiscardOldestPolicy、DiscardPolicy表示任务的拒绝策略,当线程池的线程数量达到最大值并且阻塞队列已满时,根据这些不同的策略对新提交的任务进行不同的处理。它们都实现了RejectedExecutionHandler接口。

而Worker代表我们执行的任务,我们提交的任务会被包装成Worker对象,然后再执行我们的任务或者加入阻塞队列中。

首先来看看RejectedExecutionHandler接口。

RejectedExecutionHandler接口

定义了用于处理不能被ThreadPoolExecutor执行的任务的方法。

该接口只包含了一个方法:rejectedExecution主要用于拒绝执行提交的任务。

public interface RejectedExecutionHandler 

    /*
    当ThreadPoolExecutor不能执行新任务的时候该方法将会被调用。
    当没有更多的线程或者阻塞队列已满时会发生这样的情况
    在没有其他替代方法的情况下,该方法可能会抛出未经检查的RejectedExecutionException,
    该异常将被传播到execute的调用方
     */
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);

接下来看看它的四个实现。

CallerRunsPolicy类

拒绝任务策略的实现之一:直接在调用execute方法的线程中运行被拒绝的任务,除非执行器已关闭,在这种情况下,该任务被丢弃

	public static class CallerRunsPolicy implements RejectedExecutionHandler 
        public CallerRunsPolicy()  

        /**
         * 在调用线程中执行任务,除非执行器已经被关闭,此时这个任务将被丢弃
         * @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) 
            // 如果线程池没有关闭,直接在该线程中运行此方法
            if (!e.isShutdown()) 
                r.run();
            
        
    

AbortPolicy类

当线程和阻塞队列均到达最大容量时,直接拒绝执行任务,并抛出RejectedExecutionException异常

	public static class AbortPolicy  implements RejectedExecutionHandler 
        public AbortPolicy()  

        /*
        总是直接抛出RejectedExecutionException异常
        */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) 
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " +
                                                 e.toString());
        
    

DiscardPolicy

不做任何事情,相当于直接丢弃任务。

	public static class DiscardPolicy implements RejectedExecutionHandler 
        public DiscardPolicy()  

        /**
         * 不做任何事情,这也具有丢弃任务r的效果
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) 
        
    

DiscardOldestPolicy

从名字就可以看出是丢弃最老的任务,并运行最新提交的任务。

	public static class DiscardOldestPolicy implements RejectedExecutionHandler 
        
        public DiscardOldestPolicy()  

        /*
        获取并且忽略线程池下一个要执行的任务
        如果此时线程池可用,就调用execute方法执行任务r
        除非线程池关闭,在这种情况下,任务r将被丢弃
        */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) 
            if (!e.isShutdown()) 
                e.getQueue().poll();
                e.execute(r);
            
        
    

总结

ThreadPoolExecutor的四个内部类通过实现RejectedExecutionHandler接口,实现了不同的拒绝策略,帮助我们在线程池满时拒绝执行任务。

通过这个接口,将具体拒绝的策略抽象出来,做到了解耦。所以在ThreadPoolExecutor构造函数中使用RejectedExecutionHandler作为参数,让程序员自己选择具体的拒绝策略。

Worker

Worker其实是ThreadPoolExecutor线程池中保存的工作线程,线程池主要是使用Worker来执行任务。

Worker类继承自AQS,并且实现了Runnable接口。它主要维持运行任务线程的中断控制状态,以及其他次要的信息。

它继承自AQS主要用于每次执行任务时简化锁的获取和释放。这可以防止中断用于唤醒等待任务的工作线程,而不是中断正在运行的任务。

我们实现了简单的不可重入互斥锁而不是使用ReentrantLock,因为作者不想这个Worker task在setCorePoolSize这种线程池控制方法调用时能重新获取到锁。

此外,为了压制中断直到开始执行任务,我们将锁的状态初始化为一个负值,并且直到它开始运行才清除这个值。

AQS作用

这里为什么实现了AQS呢,主要有以下两点:

  1. 将锁的粒度细化到每个Worker
    • 如果多个worker使用同一个锁的话,当一个worker running持有锁的时候,其他worker就无法执行
  2. 直接使用cas获取,避免阻塞
    • 如果这个锁使用阻塞获取,那么在多worker的情况下执行shutdown,如果某个worker此时处于running状态,无法获取锁,那么shutdown线程就阻塞住了,显然是不合理的。

注意 Worker 实现锁的目的与传统锁的意义不太一样。其主要是为了控制线程是否可interrupt(这点可以在runWorker方法中看出来,后续会讲到),以及其他的监控,如线程是否 active(正在执行任务)。

实现Runnable接口

这里实现Runnable接口是为了复用线程,减少创建线程带来的性能损耗。

因为Worker中有Thread属性,这个属性可以用于运行runnable任务,当一个任务运行完成以后,再提交

Worker属性

首先来看看它包含哪些字段:

        /** 当前worker的执行线程,如果ThreadFactory执行失败,那么其为null */
		// thread的作用就是执行worker中的run方法,也就是线程池中的真正执行任务的线程。
        final Thread thread;
        /** 当前工作线程要执行的任务,可能为null*/
        Runnable firstTask;
        /** 线程的任务计数器,记录线程已经完成多少任务 */
        volatile long completedTasks;

构造函数

设置AQS的state值为-1,表示禁止中断任务直到运行为止,也就是在创建worker期间不响应中断

Worker(Runnable firstTask) 
    setState(-1); // inhibit interrupts until runWorker
    this.firstTask = firstTask;
    // 调用ThreadFactory产生新的线程
    this.thread = getThreadFactory().newThread(this);

AQS方法实现

这里实现的是tryAcquire和tryRelease,所以这里是线程独占的锁。

// 判断线程是否正在独占资源
protected boolean isHeldExclusively() 
    return getState() != 0;

// 尝试获取线程对应的锁
protected boolean tryAcquire(int unused) 
    if (compareAndSetState(0, 1)) 
        setExclusiveOwnerThread(Thread.currentThread());
        return true;
    
    return false;

// 尝试释放获取的锁
protected boolean tryRelease(int unused) 
    setExclusiveOwnerThread(null);
    setState(0);
    return true;

锁相关方法

调用AQS的方法进行加锁与解锁。

public void lock()         acquire(1); 
public boolean tryLock()   return tryAcquire(1); 
public void unlock()       release(1); 
public boolean isLocked()  return isHeldExclusively(); 

其他

如果获取到了线程对应的锁,并且线程不为空,线程没有被中断,那么就可以中断正在运行的线程。

void interruptIfStarted() 
    Thread t;
    if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) 
        try 
            t.interrupt();
         catch (SecurityException ignore) 
        
    

委托给外部的runWorker方法运行任务。

public void run() 
    runWorker(this);

总结

本篇文章介绍了ThreadPoolExecutor的几个内部类,包括定义拒绝策略的内部类以及包装Runnable的Worker类。

通过了解ThreadPoolExecutor内部的拒绝策略,我们在使用线程池的过程中可以选择与业务相关的合适的拒绝策略,或者我们也可以模仿ThreadPoolExecutor的拒绝策略来自定义适合我们自己业务的拒绝策略。

通过解析Worker类,我们可以更清楚的了解ThreadPoolExecutor是如何工作的,它为什么继承自AQS,继承AQS的作用等,只有了解了这些知识,才能更深入的了解线程池,并且根据业务需要定义自己的线程池!

深入理解java线程池:threadpoolexecutor

线程池介绍在web开发中,服务器需要接受并处理请求,所以会为一个请求来分配一个线程来进行处理。如果每次请求都新创建一个线程的话实现起来非常简便,但是存在一个问题:如果并发的请求数量非常多,但每个线程执行的... 查看详情

线段树第二弹(区间更新)

...新、区间查询,今天,我们来接着上次的线段树问题继续深入研究。在解决线段树问题的过程中,我们会遇到要求修改区间中某一元素值的问题,当然也可能会遇到要求修改一段子区间所有值的问题--即区间更新问题。回忆一... 查看详情

重操js旧业第二弹:数据类型与类型转换

...‘asd‘ 结果:string2各种类型详解2.1number类型2.1.1概念理解:即所有实数,包括整型,浮点型,这个很好理解,但是有个 查看详情

虚拟化技术浅析第二弹之初识kubernetes

...者:京东物流杨建民一、微服务架构起源单体架构:可以理解为主要业务逻辑模块(我们编写的代码模块,不包括独立的中间件)运行在一个进程中的应用,最典型的是运行在一个Tomcat容器中,位于一个进程里。单体架构好处是... 查看详情

java进阶之路-java中的threadpoolexecutor

参考:深入理解ThreadPoolExecutor 查看详情

pythion第二弹

################################第二节################################################python中数据类型的常见的方法第一个数据类型字符串str字符串里面有很多的功能,叫方法.只要是字符串,就可以用里面的方法,方法有很多,不用刻意的去记住,因为... 查看详情

web前端-----第二弹css

CSS语法CSS规则由两个主要的部分构成:选择器,以及一条或多条声明。‘‘‘selector{property:value;property:value;...property:value}‘‘‘例如:h1{color:red;font-size:14px;}    css的四种引入方式 1.行内式       ... 查看详情

从一个程序员的角度看——微信小应用(第二弹见解)

...决定再写一篇小程序的小文,所以此篇谈谈我对小程序的理解吧。& 查看详情

第二弹:史上最全操作系统面试整理(附答案),不服来战!!(代码片段)

...给大家!!!1、简单说下你对并发和并行的理解&#x 查看详情

表单练习第二弹

---恢复内容开始---<bodytext="#33FF00"bgcolor="#33FF33"background="150358666450342800_a580xH.jpg">body里面加属性,字体颜色、背景色、背景图片。在body里面输入的属性是针对在body里面所有内容的属性。<formaction="../10.9表单/www.baidu.html"method=" 查看详情

线段树+rmq问题第二弹

   线段树+RMQ问题第二弹  上篇文章讲到了基于SparseTable解决RMQ问题,不知道大家还有没有印象,今天我们会从线段树的方法对RMQ问题再一次讨论。正式介绍今天解决RMQ问题的方法之前,我先对RMQ问题的概念再... 查看详情

lca问题第二弹

LCA问题第二弹 上次用二分的方法给大家分享了对LCA问题的处理,各位应该还能回忆起来上次的方法是由子节点向根节点(自下而上)的处理,平时我们遇到的很多问题都是正向思维处理困难而逆向思维处理比较容易,LCA问题... 查看详情

dom事件第二弹(uievent事件)

此文章主要总结UIEvent相关的事件,如有不对的地方,欢迎指正。一、uitls.js(绑定事件公共类)varfixs={‘focusin‘:{standard:‘focus‘,ie:‘focusin‘},‘focusout‘:{standard:‘blur‘,ie:‘foucsout‘},‘input‘:{standard:‘input‘,ie:‘propertychange‘}}v... 查看详情

有趣的网站-第二弹

1.预测您的死亡时间,通过输入出生日期,选择性别、BMI范围(可以通过页面下方输入身高、体重计算出)、生活态度和是否抽烟,点击查看按钮就可以得出结果。我测了我还能活52多年。。不过看着时间越来越少,心理感觉毛... 查看详情

获取免费的gpt网站指南(第二弹)

免费虽好,勿要贪杯前言GPT4free的原理是从大网站的接口中当普罗米修斯,如果换一个思路,公网上很多人都部署了自己的GPT服务,如果能够把他们利用起来,实际上我们平时问问题也够用了 查看详情

深入理解计算机系统第二章

...视频:https://www.bilibili.com/video/BV1iW411d7hd?p=2学习书本:《深入理解计算机系统》第3版。信息存储:8位=1字节;内存所有可能地址集合称为“虚拟地址空间”;每个程序对象可以视为一个“字节快”,程序本身是一个... 查看详情

cookbook学习第二弹

1.5怎样实现一个按优先级排序的队列?并且在这个队列上面每次pop操作总是返回优先级最高的那个元素带有双下划线的方法,会在需要被调用的位置自动被调用带有单下划线的变量是私有变量 下面利用类heapq模块实现一个简... 查看详情

2深入理解线程池(代码片段)

...时间,从而大大的降低系统的效率线程池讲解    ThreadPoolExecutor、AbstractExecutorService、ExecutorService和Executor之间的关系Executor是一个 查看详情