撩一撩Java线程池ThreadPoolExecutor

什么是ThreadPoolExecutor?

JDK1.5开始出现,继承关系如下:

ThreadPoolExecutor <- AbstractExecutorService <-ExecutorService <- Executor

后面两个是接口,官方注释表明主要是解决两个问题:
1.当执行大量异步任务时候线程池能够提供较好的性能,这是因为使用线程池可以使每个任务的调用开销减少。
2.线程池提供了一种资源限制和管理的手段,比如当执行一系列任务时候对线程的管理,每个ThreadPoolExecutor也保留了一些基本的统计数据,比如当前线程池完成的任务数目。

为什么系统会给我们提供线程池呢?
新建线程的缺点如下:
a. 每次new Thread新建对象性能差。
b. 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom。
c. 缺乏更多功能,如定时执行、定期执行、线程中断。
相比new Thread,四种线程池的好处在于:
a. 重用存在的线程,减少对象创建、消亡的开销,性能佳。
b. 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
c. 提供定时执行、定期执行、单线程、并发数控制等功能。

如何使用ThreadPoolExecutor

1.使用Executors工具类(让我想到了Collections)里面已有的静态方法创建

        val newFixedThreadPool = Executors.newFixedThreadPool(20)
        val newSingleThreadExecutor = Executors.newSingleThreadExecutor()
        val newCachedThreadPool = Executors.newCachedThreadPool()
        val newScheduledThreadPool = Executors.newScheduledThreadPool(20)

2.ThreadPoolExecutort提供了四个构造方法,我们可以直接创建自定义的线程池,方法1 本质上也是通过ThreadPoolExecutort创建的,只是帮我们封装好了。


构造方法

相关参数解释如下:

序号 参数名 参数类型 参数含义
1 corePoolSize int 核心线程池大小
2 maximumPoolSize int 最大线程池大小
3 keepAliveTime long 线程最大空闲时间
4 unit TimeUnit 时间单位
5 workQueue BlockingQueue<Runnable> 线程等待队列
6 threadFactory ThreadFactory 线程创建工厂
7 handler RejectedExecutionHandler 拒绝策略

我们先从系统提供的方法来深入了解下每个参数的含义

1.newFixedThreadPool(20)

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

这个方法有1 个参数,int threads 表示线程池中的数量,这个参数会直接给ThreadPoolExecutor的第一和第二个参数,也就是corePoolSize和maximumPoolSize,剩下的参数中空余时间给的是 0,创建了一个新建了阻塞队列LinkedBlockingQueue,返回值是ExecutorService。

2.newSingleThreadExecutor()

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

这个方法没有参数,和newFixedThreadPool区别是corePoolSize和maximumPoolSize两个参数给了都是 1,需要注意这里最终返回的是FinalizableDelegatedExecutorService。有个问题想一想,一个线程也要用线程池么?意义在哪里?

3.newCachedThreadPool()

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

这个看起来参数都挺全的,首先corePoolSize是 0,maximumPoolSize给了 int 最大值,keepAliveTime是 60 ,时间单位是秒,这里注意等待队列和前面两个不一样这里是SynchronousQueue。返回值是ExecutorService。

4.newScheduledThreadPool(20)

    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

这个高级点了,自己是个对象当然是集成ThreadPoolExecutor类,我们看下他的构造方法有两个,这里直接看一个参数的构造方法。返回值是ScheduledThreadPoolExecutor,这里注意和前面三个都不一样。

  public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE,
              DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
              new DelayedWorkQueue());
    }

同样这里入参和清晰了,参数名是corePoolSize核心线程数量直接给了我们ThreadPoolExecutor的第一个参数corePoolSize,第二个maximumPoolSize最大线程池大小和newCachedThreadPool一样给的是 int 最大值,第三个看起来是个常量我们看下代码中是 DEFAULT_KEEPALIVE_MILLIS = 10L ,单位是秒,这里的等待队列和前面的也不一样是DelayedWorkQueue优先级队列。

到此系统提供的四种创建线程池的参数都一一看了一遍,接下来我们看下为什么系统要提供这四种方法给我们,根据构造方法中的参数,有什么不一样的使用场景,可以先想一想不着急忘记下。

现在就开始分析下四个方法创建出的线程池的使用场景。

1. newFixedThreadPool(白话文:固定数目的线程池)

通过上面的构造方法我们知道,newFixedThreadPool核心线程池等于最大线程池,当前的线程数能够比较稳定保证一个数。能够避免频繁回收线程和创建线程。故适用于处理cpu密集型的任务,确保cpu在长期被工作线程使用的情况下,尽可能少的分配线程,即适用长期的任务。
缺点:
达到线程池最大容量后,如果有任务完成让出占用线程,那么此线程就会一直处于等待状态,而不会消亡,直到下一个任务再次占用该线程。这就可能会使用无界队列来存放排队任务,当大量任务超过线程池最大容量需要处理时,队列无线增大,会占用大量资源。

2. newSingleThreadExecutor(白话文:唯一线程线程池)

由于是唯一的嘛,最适用串行化任务。

3. newCachedThreadPool(白话文:可缓存线程的线程池)

newCacehedThreadPool 的最大特点就是,线程数量不固定。只要有空闲线程空闲时间超过keepAliveTime,就会被回收。有新的任务,查看是否有线程处于空闲状态,如果不是就直接创建新的任务。故适用用于并发不固定的短期小任务。
缺点:
线程池没有最大线程数量限制,如果大量的任务同时提交,可能导致创线程过多会而导致资源耗尽。

4. newScheduledThreadPool(白话文:定时及周期执行的线程池)

适用于定时操作一些任务

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,137评论 6 511
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,824评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,465评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,131评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,140评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,895评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,535评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,435评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,952评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,081评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,210评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,896评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,552评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,089评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,198评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,531评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,209评论 2 357