Java之ThreadPoolExcutor和四种常见的线程池

一、ThreadPoolExcutors的作用

java提供了ThreadPoolExcutors来创建一个线程池,我们为什么要用线程池呢?
1.降低资源的消耗:通过重复利用已经创建好的线程降低线程的创建和销毁带来的损耗

2.提高响应速度:因为线程池中的线程处于等待分配任务的状态,当任务来时无需创建新的线程就能执行

3.提高线程的可管理性

二、ThreadPoolExecutor构造函数参数详细介绍

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)

1.int corePoolSize(核心线程数)
线程池新建线程的时候,如果当前线程总数小于corePoolSize,则新建的是核心线程,如果超过corePoolSize,则新建的是非核心线程;核心线程默认情况下会一直存活在线程池中,即使这个核心线程啥也不干(闲置状态);如果设置了 allowCoreThreadTimeOut 为 true,那么核心线程如果不干活(闲置状态)的话,超过一定时间(时长下面参数决定),就会被销毁掉。

2.int maximumPoolSize(线程池能容纳的最大线程数量)
线程总数 = 核心线程数 + 非核心线程数。

3.long keepAliveTime(非核心线程空闲存活时长)
非核心线程空闲时长超过该时长将会被回收,主要应用在缓存线程池中,当设置了 allowCoreThreadTimeOut 为 true 时,对核心线程同样起作用。

4.TimeUnit unit 
空闲线程的存活时间(keepAliveTime 的单位)它是一个枚举类型,常用的如:TimeUnit.SECONDS(秒)、TimeUnit.MILLISECONDS(毫秒)。

5.BlockingQueue workQueue(任务队列)
当所有的核心线程都在干活时,新添加的任务会被添加到这个队列中等待处理,如果队列满了,则新建非核心线程执行任务

常用的workQueue类型:

(1)SynchornousQueue:这队列接收到任务时会直接交给线程处理,而不保留它,如果所有的线程都在工作,那就创建一   个新的线程来处理这个任务,为了保证不出现线程数达到maxnumPoolSize而不能新建线程的错误,所以使用这个类型 的队列时,maxnumPoolSize一般指定成Integer.MAX_VALUE,即无限大,然后核心线程数corePoolSize一般是0.

(2)LinkedBlockingQueue:这个队列接收到任务的时候,如果当前线程数小于核心线程数,则新建线程(核心线程)处理任务;如果当前线程数等于核心线程数,则进入队列等待。由于这个队列没有最大值限制,即所有超过核心线程数的任务都将被添加到队列中,这也就导致了 maximumPoolSize 的设定失效,因为总线程数永远不会超过 corePoolSize。

(3)ArrayBlockingQueue:可以限定队列的长度,接收到任务的时候,如果没有达到 corePoolSize 的值,则新建线程(核心线程)执行任务,如果达到了,则入队等候,如果队列已满,则新建线程(非核心线程)执行任务,又如果总线程数到了 maximumPoolSize,并且队列也满了,则发生错误。

(4)DelayQueue:队列内元素必须实现 Delayed 接口,这就意味着你传进去的任务必须先实现 Delayed 接口。这个队列接收到任务时,首先先入队,只有达到了指定的延时时间,才会执行任务。

6.ThreadFactory threadFactory(线程工厂):用来创建线程池中的线程,通常用默认的即可

7.RejectedExecutionHandler handler(拒绝策略):在线程池已经关闭的情况下和任务太多导致最大线程数和任务队列已经饱和,无法再接收新的任务,在上面两种情况下,只要满足其中一种时,在使用 execute() 来提交新的任务时将会拒绝,线程池提供了以下 4 种策略:

AbortPolicy:默认策略,在拒绝任务时,会抛出RejectedExecutionException。

CallerRunsPolicy:只要线程池未关闭,该策略直接在调用者线程中,运行当前的被丢弃的任务。

DiscardOldestPolicy:该策略将丢弃最老的一个请求,也就是即将被执行的任务,并尝试再次提交当前任务。

DiscardPolicy:该策略默默的丢弃无法处理的任务,不予任何处理。

线程池遵循的原则:
其会优先创建核心线程,执行任务,当核心线程增加CorePoolSize后,我们会把任务添加到work Queue中,当work Queue里面的任务也塞满了,线程池就会创建非核心线程执行去执行任务,当线程达到maximumPoolSize时候和work queue也达最大值时候我们会执行对应的拒绝策略

三、4类常见的线程池

newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。

newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
其实他们都是通过ThreadPoolExcutors创建的

1.CachedThreadPool
由Executors的newCachedThreadPool方法创建,不存在核心线程,只存在数量不定的非核心线程,而且其数量最大值为Integer.MAX_VALUE。当线程池中的线程都处于活动时(全满),线程池会创建新的线程来处理新的任务,否则就会利用新的线程来处理新的任务,线程池中的空闲线程都有超时机制,默认超时时长为60s,超过60s的空闲线程就会被回收。和FixedThreadPool不同的是,CachedThreadPool的任务队列其实相当于一个空的集合,这将导致任何任务都会被执行,因为在这种场景下SynchronousQueue是不能插入任务的,SynchronousQueue是一个特殊的队列,在很多情况下可以理解为一个无法储存元素的队列。从CachedThreadPool的特性看,这类线程比较适合执行大量耗时较小的任务。当整个线程池都处于闲置状态时,线程池中的线程都会因为超时而被停止回收,几乎是不占任何系统资源。实现方式如下:

public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>(),
                                  threadFactory);
}

2.FixedThreadPool
由Executors的newFixedThreadPool方法创建。它是一种线程数量固定的线程池,当线程处于空闲状态时,他们并不会被回收,除非线程池被关闭。当所有的线程都处于活动状态时,新的任务都会处于等待状态,直到有线程空闲出来。FixedThreadPool只有核心线程,且该核心线程都不会被回收,这意味着它可以更快地响应外界的请求。jdk实现如下:FixedThreadPool没有额外线程,只存在核心线程,而且核心线程没有超时机制,而且任务队列没有长度的限制。

public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>(),
                                  threadFactory);
}

3.ScheduledThreadPool

通过Executors的newScheduledThreadPool方式创建,核心线程数量是固定的,而非核心线程是没有限制的,并且当非核心线程闲置时它会被立即回收,ScheduledThreadPool这类线程池主要用于执行定时任务和具有固定时期的重复任务,实现方法如下:

public static ScheduledExecutorService newScheduledThreadPool(
        int corePoolSize, ThreadFactory threadFactory) {
    return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
public ScheduledThreadPoolExecutor(int corePoolSize,
                                   ThreadFactory threadFactory) {
    super(corePoolSize, Integer.MAX_VALUE,
          DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
          new DelayedWorkQueue(), threadFactory);
}

4.SingleThreadPool
通过Executors的newSingleThreadExecutor方法来创建。这类线程池内部只有一个核心线程,它确保所有的任务都在同一个线程中按顺序执行。SingleThreadExecutor的意义在于统一所有外界任务一个线程中,这使得这些任务之间不需要处理线程同步的问题,实现方式如下:

public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>(),
                                threadFactory));
}

四、为什么《阿里巴巴Java开发手册》上要禁止使用Executors来创建线程池

Executors创建出来的线程池使用的全都是无界队列,而使用无界队列会带来很多弊端,最重要的就是,它可以无限保存任务,因此很有可能造成OOM异常。同时在某些类型的线程池里面,使用无界队列还会导致maxinumPoolSize、keepAliveTime、handler等参数失效

五、我们用代码测试下


import java.util.*;
import java.lang.*;
import java.io.Serializable;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

class Rextester
{
private static int produceTaskSleepTime = 5;
    private static int consumeTaskSleepTime = 1000;
    private static int taskMaxNumber = 10; //定义最大添加10个线程到线程池中
    public static void main(String args[]) {

//构造一个线程池
//该策略默默的丢弃无法处理的任务,不予任何处理。
        ThreadPoolExecutor threadPool = getTpe("DiscardPolicy");
        //该策略将丢弃最老的一个请求,也就是即将被执行的任务,并尝试再次提交当前任务。
        //ThreadPoolExecutor threadPool = getTpe("DiscardOldestPolicy");
        for (int i = 1; i <= taskMaxNumber; i++) {
            try {
                //添加任务到线程池,我们要知道添加的任务是Runnable
                String value = "value is: " + i;
                System.out.println("put:" + value);
                threadPool.execute(new ThreadPoolTask(value));
                //线程休息一段时间,方便我们分析结果
                Thread. sleep(produceTaskSleepTime);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

   /**
    * 线程池执行的任务
    */
    public static class ThreadPoolTask implements Runnable {
    //保存任务所需要的数据
        private String value;
        public ThreadPoolTask(String value) {
            this.value = value;
        }
        public void run() {
            //打印语句
            System. out.println("start------" + value);
            try {
                Thread. sleep(consumeTaskSleepTime);
            } catch (Exception e) {
                e.printStackTrace();
            }
            value = "";
        }
    }

   /**
    *初始化线程池
    */
    public static ThreadPoolExecutor getTpe(String type) {
    if ("DiscardOldestPolicy".equals(type)) {
            return  new ThreadPoolExecutor(2, 4, 3,
                    TimeUnit. SECONDS, new ArrayBlockingQueue<Runnable>(3),
                    new ThreadPoolExecutor.DiscardOldestPolicy());
    } else {
            return  new ThreadPoolExecutor(2, 4, 3,
                    TimeUnit. SECONDS, new ArrayBlockingQueue<Runnable>(3),
                    new ThreadPoolExecutor.DiscardPolicy());
    }
        //DiscardPolicy DiscardOldestPolicy
    }
}

运行结果:

put:value is: 1
start------value is: 1
put:value is: 2
start------value is: 2
put:value is: 3
put:value is: 4
put:value is: 5
put:value is: 6
start------value is: 6
put:value is: 7
start------value is: 7
put:value is: 8
put:value is: 9
put:value is: 10
start------value is: 3
start------value is: 4
start------value is: 5

我们把上面的生成线程池的代码不//

ThreadPoolExecutor threadPool = getTpe("DiscardOldestPolicy");

运行结果如下

put:value is: 1
start------value is: 1
put:value is: 2
start------value is: 2
put:value is: 3
put:value is: 4
put:value is: 5
put:value is: 6
start------value is: 6
put:value is: 7
start------value is: 7
put:value is: 8
put:value is: 9
put:value is: 10
start------value is: 8
start------value is: 9
start------value is: 10

部分内容参考了博客:https://blog.csdn.net/qq_34835058/article/details/80497602

(0)

相关推荐

  • 透彻了解线程池

    线程池 1.1 构造参数 参数名 类型 含义 corePoolSize int 核心线程数,在线程池完成初始化后,默认情况下,线程池中并没有任务线程,线程池会等待有任务到来时,再去创建新线程去执行任务 ...

  • 万字长文爆肝线程池

    加个"星标",及时接收最新文章 这是程序员cxuan 的第 59 篇原创文章 更多文章见 https://github.com/crisxuan/bestJavaer 我们知道,线 ...

  • 面试官:怎样去运用线程池?工作中如何使用?

    面试官:怎样去运用线程池?工作中如何使用? 工作中,我们有时候需要实现一些耗时的任务.比如:将 Word 转换成 PDF 存储的需求. 假设我们不使用线程池.那么每次请求都会开启新的线程,如果请求过多 ...

  • 造轮子-ThreadPoolExecutor

    以下代码的实现逻辑出自于公众号 码农翻身 <你管这破玩意叫线程池?> - PS:刘欣老师在我心中是软件技术行业的大刘. 线程池接口 public interface Executor { ...

  • 四种常见恒流源电路及作有源负载的应用

    维库电子市场网> 技术资料> 基础电子 基本的恒流源电路主要是由输入级和输出级构成,输入级提供参考电流,输出级输出需要的恒定电流.恒流源电路就是要能够提供一个稳定的电流以保证其它电路稳定工 ...

  • 你有荨麻疹吗农村四种常见叶子,专治荨麻疹,洗几次就除根

    你患过荨麻疹吗?你见过别人得荨麻疹吗? 荨麻疹很难受,一想到荨麻疹,我就忍不住起鸡皮疙瘩.我想我所有患有或见过荨麻疹的朋友都有同样的感受.首先,身体的某些部位不停地出现红肿,非常痒,而且越挠越痒.其次 ...

  • 手把手教你学舌诊 四种常见舌象用药一看就会!

    舌诊是中医常见诊断辅助方法,简便易行,能较客观准确地反映病情,是了解病情的发展变化和辨证的重要依据. 在学习舌诊之前,我们要明白舌诊的临床意义是什么? 一判断邪正盛衰 首先我们来看看正常的舌象: 舌体 ...

  • 83岁老中医谈四种常见老年病的治疗(纯干货,收藏)

    I导读:满满的干货,文章中零金碎玉诸多,拿出笔记,一起学习吧.(编辑/小谢) 常见四种老年性疾病的防治 作者/邹云翔 生老病死是人生不可避免的规律,若养生有道,寿命可延至120岁.<素问·上古天 ...

  • 切莫忽视!十四种常见癌症的早期信号

    癌症固然可怕,但让我们稍感安慰的是,所有的癌症都不是一夜之间发生的,也不会在一夜之间变得不可收拾,而且它在"发展"过程中不可避免地会露出一些马脚.如果我们能关注这些蛛丝马迹,并做好 ...

  • 浅谈四种常见的精神抑郁症动物应激模型检测方法与模型特点

    摘要:应激可以引起人类及动物的抑郁,而抗抑郁剂通常又可以纠正应激引起的异常行为.因此,应激可以作为制造抑郁模型的手段.其应激模型包括急性应激.慢性应激和慢性轻度不可预见性刺激模型等. 应激可以引起人类 ...

  • 小龙虾十四种常见疾病及其防治方法!!

    小龙虾是我们餐桌上常见的一道特色菜,在养殖过程中有时候会发病,在防治过程需要掌握哪些方法呢? 1.软壳病 病因:体内缺钙,另外,光照不足.pH值长期偏低,池底淤泥过厚.虾苗密度过大.长期投喂单一饵料也 ...

  • 糟糕!食物中毒了!为你揭秘这四种常见的食物,有助于解食物中毒

    在我们日常的饮食当中总会避免不了出现食物中毒的现象,多半是因为食品安全问题和食物相生相克引起的.当出现食物中毒时可以喝些糖盐水来解毒,或是喝绿豆汤.适量吃猪血.喝茶都能起到解毒的效果!但是食物中毒比较 ...

  • 子宫脱垂的四种常见治疗方法原平

    生活中有些人因为盆腔做过手术或者是经常吸烟以及多吃分娩等因素导致了子宫脱垂的出现.这是一种比较严重的妇科疾病.那你知道子宫脱垂如何有效治疗吗?其实治疗的方法有不少哦,朋友们不妨跟着来瞧瞧吧. 子宫脱垂 ...