JavaGuide知识点整理——线程池的最佳实践

唯有努力不欺人丶  IP属地: 北京
1字数 2,409阅读 2,252

重新声明下,虽然开这系列笔记的时候就说了这是最近看javaguide网站,然后为了加深记忆也为了好知识一起分享,所以把网站中的知识点搬运了一遍,其中掺杂这我自己的理解和实践等。然后各位如果感觉去可以去看原文。附上链接:https://snailclimb.gitee.io/javaguide/#/./docs/java/concurrent/java-thread-pool-best-practices

线程池在实际项目中的使用场景

线程池一般用于执行多个不相关联的耗时任务。没有多线程的情况下,任务使用顺序执行,使用了线程池的话可以让多个不相关联的任务同时执行。

假设我们要执行三个不相关的耗时任务。使用线程池前后的区别如下:


使用多线程前后

注意这里使用多线程执行不同的任务,可以用一个countDownLatch等待子任务执行完成才继续往下返回结果。

线程池最佳实践

使用ThreadPoolExecutor的构造函数声明线程池

线程池必须手动通过ThreadPoolExecutor的构造函数声明,避免使用Executors类的newFixedThreadPool和newCachedThreadPool,因为可能会有oom的风险。
说白了就是使用有界队列,控制线程创建数量。
除了避免OOM的原因外,不推荐使用Executors提佛那个的快捷线程池还有两个原因:

  1. 实际使用中需要根据自己机器的性能,业务场景来手动配置线程池的参数。比如核心线程数,最大线程数,使用的任务队列,拒绝策略等。
  2. 我们应该显示的给我们的线程池命名,这样有助于我们定位问题。

监测线程池运行状态

我们可以通过一些手段来检测线程池的运行状态,比如SpringBoot中的Actuator组件。
除此之外我们还可以通过ThreadPoolExecutor的相关api做一个简陋的监控。从下图可以看出,ThreadPoolExecutor提供了互殴线程池当前的线程数和活跃线程数,已执行完成的任务数,正在排队的任务数等任务。


ThreadPoolExecutor获取当前线程池信息

建议不同类别的业务用不同的线程池

很多人在实际项目中会有类似这样的问题:我的项目中多个业务需要用到线程池,是为每个线程池都定义一个还是说定义一个公共的线程池呢?
一般建议不同的业务使用不同的线程池, 配置线程池的时候根据当前业务情况对当前线程池进行配置,因为不同的业务的并发以及对资源的使用情况都不同,重新优化系统性能瓶颈相关的业务。

下面我们看一个线程池运用不当的线上事故案例:


线程池使用不当demo

图中的代码可能会存在死锁的情况。为什么呢?下面我们捋一捋。
试想这么一个极端现象:如果核心线程数是n,父任务数量也为n。把核心线程全部占用。然后父任务下的子任务也需要用线程。在任务队列中阻塞等待获取线程。而父任务在等待子任务执行完成,子任务等待父任务释放线程资源好获取线程。也就造成了死锁。

解决方法也很简单,新增加一个用于执行子任务的线程池专门为其服务。

要给线程池命名

初始化线程池的时候需要显示命名(设置线程池名称前缀),有利于定位问题。
默认情况下创建的线程名字类似pool-1-thread-n这样的,没有业务含义,不利于我们定位问题。
给线程池里的线程命名通常有两种方式:

  1. 利用谷歌的ThreadFactoryBuilder给线程池里的线程命名
    public static void main(String[] args) throws Exception {
        ThreadFactory threadFactory =
            new ThreadFactoryBuilder().setNameFormat("用来测似的线程池" + "-%d").setDaemon(true).build();
       ThreadPoolExecutor threadPoolExecutor =
           new ThreadPoolExecutor(2,10,1l,TimeUnit.SECONDS,new ArrayBlockingQueue<>(10),threadFactory);
       for(int i = 0;i<10;i++){
           threadPoolExecutor.execute(()->{
               System.out.println("当前线程名:"+Thread.currentThread().getName());
           });
       }
    }

如上代码就是命名了,下面是如果这个线程池里的线程报错,可以很容易定位。


定位到问题出自这个线程池
  1. 也可以自己实现ThreadFactor来给线程池里的线程命名
public class Test {

    public static void main(String[] args) throws Exception {
        MyThreadFactory threadFactory = new MyThreadFactory(Executors.defaultThreadFactory(),"测试线程池");
       ThreadPoolExecutor threadPoolExecutor =
           new ThreadPoolExecutor(2,10,1l,TimeUnit.SECONDS,new ArrayBlockingQueue<>(10),threadFactory);
       for(int i = 0;i<10;i++){
           threadPoolExecutor.execute(()->{
               System.out.println("当前线程名:"+Thread.currentThread().getName());
           });
       }
    }
}

final  class MyThreadFactory implements ThreadFactory{

    ThreadFactory threadFactory;
    String name;
    AtomicInteger i = new AtomicInteger(1);

    MyThreadFactory(ThreadFactory threadFactory,String name){
        this.threadFactory = threadFactory;
        this.name = name;
    }

    @Override
    public Thread newThread(Runnable r) {
        Thread t = threadFactory.newThread(r);
        t.setName(name+i.getAndIncrement());
        return t;
    }
}

其实这种写法就是单纯的包了一层,每一个线程都手动的setName给设置了个名字。具体要用那种写法都可以,反正我是觉得自己设置的这个最开始写要麻烦点,但是每次用方便。谷歌的方法不需要创建什么工具类,但是每次创建都要设置。

正确的配置线程池参数

常规操作
首先这里要说一个常识:并不是线程越多越好。比如一个很小的任务,一个人做要1小时,但是60个人也不会是一分钟。甚至因为人多交流成本太大。指不定还会用时更长。
线程数量过多的影响也是和我们分配多少人做事一样。对于多线程的场景主要是增加了上下文切换成本。

类比我们人类通过合作做某件事,我们可以知道线程池过大过小都不好,合适才是最好的。
如果我们设置的线程池数量太小,同一时间有大量任务/请求需要处理,可能会导致大量的请求/任务在任务队列中排队等待执行,甚至出现队列满了之后任务/请求无法处理的情况。或者大量任务堆积在队列中导致OOM,这样很明显是有问题的。CPU根本没有得到充分利用。
但是我们设置线程数量过大,大量线程可能同时争夺CPU资源,这样会导致大量的上下文切换,从而增加线程的执行时间,影响整体执行效率。

网上有一个简单并且通用的公式:

  • CPU密集型任务(n+1):这种任务消耗的是CPU资源,可以将线程数设置为cpu核心数+1.比CPU核心数多一个是为了防止线程偶发的缺页中断或者其他原因导致的任务暂停。一旦任务暂停,cpu就会处于空闲状态,这个时候多出的一个线程就可以充分利用CPU时间。
  • I/O密集型任务(2n):这种任务应用起来系统会占用大部分时间处理I/O交互,而线程处理I/O的时间段不会占用CPU来处理,这时候就可以把CPU交出给其他线程使用。所以我们可以多配置一些线程。比如2N。

如何判断是CPU密集任务还是IO密集任务?

CPU密集型简单理解就是利用CPU计算能力的任务。比如在内存中对大量数据进行排序。但凡涉及到网络读取,文件读取这类都是IO密集型。这类任务的特点是CPU计算消耗时间相比于等待IO操作完成的时间来说很少。大部分时间都花费在等待IO操作完成上。

美团的骚操作

美团技术团队在java线程池实现原理以及在内图案业务中的实践这篇文章中介绍到对线程池参数实现可自定义配置的思路和方法。

美团技术团队的思路是主要对线程池的核心参数实现自定义可配置。这三个核心参数是:

  • corPoolSize:核心线程数定义了最小可以同时运行的线程数量。
  • maximumPoolSize:当队列中存放的任务达到队列容量时,当前可以同事运行的线程数量变为最大线程数。
  • workQueue:当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话新任务会被存放在队列中。
  • 还包括一些队列长度等。
动态修改线程池参数

实现的重点是基于ThreadPoolExecutor的几个方法,我们只需要维护ThreadPoolExecutor的实例,并在需要修改的时候拿到实例修改其参数即可。基于这个原理我们做到线程池参数的动态化,可视并且可配置。效果如下图。


动态修改线程池参数

其实我们基于这个思想可以做的就更多了。比如一些监控:线程池活跃度,告警,执行任务的频率和耗时,Reject异常等等。从而避免故障或者加速故障的修复。感觉美团技术团队对于线程池的实践介绍比较浅显易懂,感兴趣的可以自己去看下。

本篇笔记就记到这里,如果稍微帮到你了记得点个喜欢点个关注。也祝大家工作顺顺利利,生活健健康康~!

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
9人点赞
更多精彩内容,就在简书APP
"小礼物走一走,来简书关注我"
还没有人赞赏,支持一下
唯有努力不欺人丶  我相信文字是有力量的。<br>敲代码是种乐趣.
总资产185共写了97.9W字获得1,904个赞共3,056个粉丝
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 228,443评论 6 532
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 98,530评论 3 416
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 176,407评论 0 375
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 62,981评论 1 312
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 71,759评论 6 410
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 55,204评论 1 324
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 43,263评论 3 441
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 42,415评论 0 288
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 48,955评论 1 336
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 40,782评论 3 354
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 42,983评论 1 369
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 38,528评论 5 359
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,222评论 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 34,650评论 0 26
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 35,892评论 1 286
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 51,675评论 3 392
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 47,967评论 2 374

被以下专题收入,发现更多相似内容

推荐阅读更多精彩内容