Java并发编程实战
王宝令
资深架构师
立即订阅
15151 人已学习
课程目录
已完结 50 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 你为什么需要学习并发编程?
免费
学习攻略 (1讲)
学习攻略 | 如何才能学好并发编程?
第一部分:并发理论基础 (13讲)
01 | 可见性、原子性和有序性问题:并发编程Bug的源头
02 | Java内存模型:看Java如何解决可见性和有序性问题
03 | 互斥锁(上):解决原子性问题
04 | 互斥锁(下):如何用一把锁保护多个资源?
05 | 一不小心就死锁了,怎么办?
06 | 用“等待-通知”机制优化循环等待
07 | 安全性、活跃性以及性能问题
08 | 管程:并发编程的万能钥匙
09 | Java线程(上):Java线程的生命周期
10 | Java线程(中):创建多少线程才是合适的?
11 | Java线程(下):为什么局部变量是线程安全的?
12 | 如何用面向对象思想写好并发程序?
13 | 理论基础模块热点问题答疑
第二部分:并发工具类 (14讲)
14 | Lock和Condition(上):隐藏在并发包中的管程
15 | Lock和Condition(下):Dubbo如何用管程实现异步转同步?
16 | Semaphore:如何快速实现一个限流器?
17 | ReadWriteLock:如何快速实现一个完备的缓存?
18 | StampedLock:有没有比读写锁更快的锁?
19 | CountDownLatch和CyclicBarrier:如何让多线程步调一致?
20 | 并发容器:都有哪些“坑”需要我们填?
21 | 原子类:无锁工具类的典范
22 | Executor与线程池:如何创建正确的线程池?
23 | Future:如何用多线程实现最优的“烧水泡茶”程序?
24 | CompletableFuture:异步编程没那么难
25 | CompletionService:如何批量执行异步任务?
26 | Fork/Join:单机版的MapReduce
27 | 并发工具类模块热点问题答疑
第三部分:并发设计模式 (10讲)
28 | Immutability模式:如何利用不变性解决并发问题?
29 | Copy-on-Write模式:不是延时策略的COW
30 | 线程本地存储模式:没有共享,就没有伤害
31 | Guarded Suspension模式:等待唤醒机制的规范实现
32 | Balking模式:再谈线程安全的单例模式
33 | Thread-Per-Message模式:最简单实用的分工方法
34 | Worker Thread模式:如何避免重复创建线程?
35 | 两阶段终止模式:如何优雅地终止线程?
36 | 生产者-消费者模式:用流水线思想提高效率
37 | 设计模式模块热点问题答疑
第四部分:案例分析 (4讲)
38 | 案例分析(一):高性能限流器Guava RateLimiter
39 | 案例分析(二):高性能网络应用框架Netty
40 | 案例分析(三):高性能队列Disruptor
41 | 案例分析(四):高性能数据库连接池HiKariCP
第五部分:其他并发模型 (4讲)
42 | Actor模型:面向对象原生的并发模型
43 | 软件事务内存:借鉴数据库的并发经验
44 | 协程:更轻量级的线程
45 | CSP模型:Golang的主力队员
结束语 (1讲)
结束语 | 十年之后,初心依旧
用户故事 (2讲)
用户来信 | 真好,面试考到这些并发编程,我都答对了!
3 个用户来信 | 打开一个新的并发世界
Java并发编程实战
登录|注册

22 | Executor与线程池:如何创建正确的线程池?

王宝令 2019-04-18
虽然在 Java 语言中创建线程看上去就像创建一个对象一样简单,只需要 new Thread() 就可以了,但实际上创建线程远不是创建一个对象那么简单。创建对象,仅仅是在 JVM 的堆里分配一块内存而已;而创建一个线程,却需要调用操作系统内核的 API,然后操作系统要为线程分配一系列的资源,这个成本就很高了,所以线程是一个重量级的对象,应该避免频繁创建和销毁
那如何避免呢?应对方案估计你已经知道了,那就是线程池。
线程池的需求是如此普遍,所以 Java SDK 并发包自然也少不了它。但是很多人在初次接触并发包里线程池相关的工具类时,多少会都有点蒙,不知道该从哪里入手,我觉得根本原因在于线程池和一般意义上的池化资源是不同的。一般意义上的池化资源,都是下面这样,当你需要资源的时候就调用 acquire() 方法来申请资源,用完之后就调用 release() 释放资源。若你带着这个固有模型来看并发包里线程池相关的工具类时,会很遗憾地发现它们完全匹配不上,Java 提供的线程池里面压根就没有申请线程和释放线程的方法。
class XXXPool{
// 获取池化资源
XXX acquire() {
}
// 释放池化资源
void release(XXX x){
}
}

线程池是一种生产者 - 消费者模式

为什么线程池没有采用一般意义上池化资源的设计方法呢?如果线程池采用一般意义上池化资源的设计方法,应该是下面示例代码这样。你可以来思考一下,假设我们获取到一个空闲线程 T1,然后该如何使用 T1 呢?你期望的可能是这样:通过调用 T1 的 execute() 方法,传入一个 Runnable 对象来执行具体业务逻辑,就像通过构造函数 Thread(Runnable target) 创建线程一样。可惜的是,你翻遍 Thread 对象的所有方法,都不存在类似 execute(Runnable target) 这样的公共方法。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《Java并发编程实战》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(45)

  • undifined
    思考题:
    1. 给线程池设置名称前缀
    ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
    threadPoolTaskExecutor.setThreadNamePrefix("CUSTOM_NAME_PREFIX");

    2. 在ThreadFactory中自定义名称前缀
    class CustomThreadFactory implements ThreadFactory {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread("CUSTOM_NAME_PREFIX");
                return thread;
            }
        }

    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10,
                    100,
                    120,
                    TimeUnit.SECONDS,
                    new LinkedBlockingQueue<>(),
                    new CustomThreadFactory(),
                    new ThreadPoolExecutor.AbortPolicy()
            );
    2019-04-18
    41
  • Seven Blue
    回答下Lrwin和张天屹同学的问题:当线程池中无可用线程,且阻塞队列已满,那么此时就会触发拒绝策略。对于采用何种策略,具体要看执行的任务重要程度。如果是一些不重要任务,可以选择直接丢弃。但是如果为重要任务,可以采用降级处理,例如将任务信息插入数据库或者消息队列,启用一个专门用作补偿的线程池去进行补偿。所谓降级就是在服务无法正常提供功能的情况下,采取的补救措施。具体采用何种降级手段,这也是要看具体场景。技术的世界里没有一尘不变的方案。另外,看到很多同学都提到让老师多讲讲源码,其实我觉得真没必要,老师目前的思路起到提纲契领的作用,让我们有大的思路,有全局观,具体细节我觉得大家私下去研究更合适。小弟不才,可以加微信(SevenBlue)一起讨论。

    作者回复: 👍我觉得那些源码用的时候看一下就可以了,现在都是用开源项目,天天都得看源码,看源码能局部最优而已

    2019-04-22
    32
  • 榣山樵客™
    guava的ThreadFactoryBuilder.setNameFormat可以指定一个前缀,使用%d表示序号;
    或者自己实现ThreadFactory并制定给线程池,在实现的ThreadFactory中设定计数和调用Thread.setName

    作者回复: 👍

    2019-04-18
    1
    8
  • 任大鹏
    老师的文中已经给出了一个答案:
    threadFactory:通过这个参数你可以自定义如何创建线程,例如你可以给线程指定一个有意义的名字。
    2019-04-18
    8
  • 海鸿
    1.利用guava的ThreadFactoryBuilder
    2.自己实现ThreadFactory

    作者回复: 👍

    2019-04-18
    6
  • 张天屹
    老师你好,使用有界队列虽然避免了OOM 但是如果请求量太大,我又不想丢弃和异常的情况下一般怎么实践呢。我对降级这一块没经验,我能直观想到的就是存放在缓存,如果缓存内存也不够了就只能持久化了

    作者回复: 可以放数据库,放mq,redis,本地文件都可以,具体要看实际需求

    2019-04-18
    6
  • 随风🐿
    老师,有个问题一直不是很明确,①一个项目中如果多个业务需要用到线程池,是定义一个公共的线程池比较好,还是按照业务定义各自不同的线程池?②如果定义一个公共的线程池那里面的线程数的理论值应该是按照老师前面章节讲的去计算吗?还是按照如果有多少个业务就分别去计算他们各自创建线程池线程数的加和?③如果不同的业务各自定义不同的线程池,那线程数的理论值也是按照前面的去计算吗?

    作者回复: 建议不同类别的业务用不同的线程池,至于线程池的数量,各自计算各自的,然后去做压测。虽然你的系统有多个线程池,但是并不是所有的线程池里的线程都是忙碌的,你只需要针对有性能瓶颈的业务优化就可以了。

    2019-04-29
    1
    5
  • Red Cape
    请问老师,有界队列的长度怎么确定呢

    作者回复: 看场景,拍脑门

    2019-04-22
    5
  • linqw
    最近打算分析下Executor系列源码,先分析了下FutureTask源码,https://juejin.im/post/5d08be8ce51d455d6c0ad925,老师有空帮忙看下哦

    作者回复: 👍

    2019-06-19
    3
  • 西西弗与卡夫卡
    线程命名常用方法是:线程的构造函数传入名字,或者调用setName设置
    2019-04-18
    3
  • 曾轼麟
    public class ReNameThreadFactory implements ThreadFactory {
        /**
         * 线程池编号(static修饰)(容器里面所有线程池的数量)
         */
        private static final AtomicInteger POOLNUMBER = new AtomicInteger(1);

        /**
         * 线程编号(当前线程池线程的数量)
         */
        private final AtomicInteger threadNumber = new AtomicInteger(1);

        /**
         * 线程组
         */
        private final ThreadGroup group;

        /**
         * 业务名称前缀
         */
        private final String namePrefix;


        /**
         * 重写线程名称(获取线程池编号,线程编号,线程组)
         *
         * @param prefix 你需要指定的业务名称
         */
        public ReNameThreadFactory(@NonNull String prefix) {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                    Thread.currentThread().getThreadGroup();
            //组装线程前缀
            namePrefix = prefix + "-poolNumber:" + POOLNUMBER.getAndIncrement() + "-threadNumber:";
        }


        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                    //方便dump的时候排查(重写线程名称)
                    namePrefix + threadNumber.getAndIncrement(),
                    0);
            if (t.isDaemon()) {
                t.setDaemon(false);
            }
            if (t.getPriority() != Thread.NORM_PRIORITY) {
                t.setPriority(Thread.NORM_PRIORITY);
            }
            return t;
        }
    }

    作者回复: 👍

    2019-04-21
    2
  • 晓杰
    希望老师把线程异常处理这块可以再深入讲一讲
    2019-04-19
    2
  • magict4
    老师您好,请问有什么推荐的替代 Executors 的方案吗?

    作者回复: 我也没用其他的

    2019-04-18
    2
  • 朱晋君
    我们项目中用了guava的new ThreadFactoryBuilder().setNameFormat()

    老师,请教个问题,在工程中,线程池的定义一般是在全局还是局部呢?如果全局的话,是不用shutdown吗?不关闭线程池有没有问题呢?

    作者回复: 一般都全局,如果需要优雅退出就需要shutdown。不关闭,会有coresize个线程一直回收不了。

    2019-04-18
    2
  • 郑晨Cc
    可参照SDK中的 DefaultThreadFactory 自定义DYIThreadFactory
    static class DIYThreadFactory implements ThreadFactory {
            private static final AtomicInteger poolNumber = new AtomicInteger(1);
            private final ThreadGroup group;
            private final AtomicInteger threadNumber = new AtomicInteger(1);
            private final String namePrefix;

            DIYThreadFactory(String diyName) {
                SecurityManager s = System.getSecurityManager();
                group = (s != null) ? s.getThreadGroup() :
                                      Thread.currentThread().getThreadGroup();
                namePrefix = diyName +
                             "-thread-";
            }

            public Thread newThread(Runnable r) {
                Thread t = new Thread(group, r,
                                      namePrefix + threadNumber.getAndIncrement(),
                                      0);
                if (t.isDaemon())
                    t.setDaemon(false);
                if (t.getPriority() != Thread.NORM_PRIORITY)
                    t.setPriority(Thread.NORM_PRIORITY);
                return t;
            }
        }

    ExecutorService executor = Executors.newFixedThreadPool(4,new DIYThreadFactory("xxx"));

    作者回复: 👍

    2019-04-18
    2
  • 星辰
    老师,有一个问题想问一下:

    如果corePoolSize为10,maxinumPoolSize为20,而此时线程池中有15个线程在运行,过了一段时间后,其中有3个线程处于等待状态的时间超过keepAliveTime指定的时间,则结束这3个线程,此时线程池中则还有12个线程正在运行;若有六个线程处于等待状态的时间超过keepAliveTime指定的时间,则只会结束5个线程,此时线程池中则还有10个线程,即核心线程数。

    是这样吗?

    作者回复: 是的

    2019-04-22
    1
  • 密码123456
    有个问题,不能理解。既然execute使用newfixedthreadpool设置固定的线程池。在实际使用execute执行并发任务,cpu利用率会过高。按照道理说,只有开始的时候,线程会创建消耗资源。在创建之后都不会消耗资源才对啊?
    2019-04-19
    1
  • Lrwin
    如果线程池处理的任务非常重要,建议自定义自己的拒绝策略;并且在实际工作中,自定义的拒绝策略往往和降级策略配合使用。

    老师,请问这个怎么理解?能举个例子吗?
    2019-04-18
    1
  • 张三
    打卡!
    2019-04-18
    1
  • Uncle Drew
    老师请教一下,如果线上系统宕机了,线程池中的阻塞队列怎么处理才能保证任务不丢失

    作者回复: 可以把所有任务先存入数据库,处理完一条就在数据库里删除一条。单纯依赖单机的内存是无法解决的。

    2019-12-06
收起评论
45
返回
顶部