《码出高效-Java开发手册》读书笔记 第七章(二)

接着前面的继续做笔记,现在到了线程池。线程池平时项目里,使用的非常多,但一直停留在一知半解的状态,也就是知道一个execute(Runnable)的水平,也基本不使用自定义线程池,这次好好的学一下。

先看看书上线程池的作用:

  • 利用线程池管理并复用线程、控制最大并发数。
  • 实现任务线程队列缓存策略和拒绝机制。
  • 实现某些与时间相关的功能,如定时执行、周期执行等。
  • 隔离线程环境。

为了更好的使用这些功能,需要在创建线程池时,选择合适的参数,先看下他的构造方法:


image.png
  • corePoolSize,线程池保留的最小线程数。如果线程池中的线程少于此数目,则在执行execute()时创建。
  • maximumPoolSize,线程池中允许拥有的最大线程数。
  • keepAliveTime、unit,当线程闲置时,保持线程存活的时间。
  • workQueue,工作队列,存放提交的等待任务,该类队列是生产者消费者模型队列。
  • threadFactory,线程工厂,它用来产生一组相同任务的线程。
  • handler,执行拒绝策略的对象,如果不实现,出现拒绝服务时,默认则是抛出异常。

再看下线程池是如何工作的,从源码的注释来说:


线程池基本策略
  1. 当接收到一个任务时,如果线程池中运行的线程数小于corePoolSize核心线程,则新建一个线程。
  2. 如果所有运行的核心线程都都在忙,超出核心线程处理的任务,执行器更多地选择把任务放进队列,而不是新建一个线程。
  3. 如果一个任务提交不了到队列,在不超出最大线程数量情况下,会新建线程,否则就出现拒绝服务。

显然,队列workQueue的选择就显得很有必要,再来看下注释里的3类队列使用:

  • Direct handoffs:直接提交,代表类型SynchronousQueue。特点是不保持,任务直接提交给线程,如果没有空闲线程,则自行开启线程。
  • Unbounded queues:无限提交,代表类型LinkedBlockingQueue。由于队列本身没有上限,所以当核心线程都在工作时,会直接把任务保存在该队列中,所以同时执行的线程数最多只会是corePoolSize,自然maximumPoolSize参数也没有了意义。
  • Bounded queues:有限提交,代表类型ArrayBlockingQueue。和无限提交类似,不过就是队列有了上限,超出队列的任务,会尝试去开启线程,如果运行线程总数超过maximumPoolSize,则会出现拒绝服务。

知道了这些策略,我们来看下JDK提供的几个常用的线程池,看一下使用范例:

  1. newFixedThreadPool,采用Bounded queues,可以长期保持固定的线程数工作,会积压任务,适合长期稳定的任务,但由于没有拒绝策略,可能会造成OOM
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
  1. newCachedThreadPool,采用Direct handoffs,没有核心线程,会复用已有线程,适合短时间的密集任务,同样有OOM风险
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
  1. ScheduledThreadPoolExecutor,支持定时以及周期性任务执行,比Timer更安全,功能更强大,同样有OOM风险
    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE,
              DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
              new DelayedWorkQueue());
    }

前面的以前多少有接触一些, 书上更让我感兴趣的是后面的一部分:

Executors中默认的线程工厂和拒绝策略过于简单,通常对用户不友好,线程工厂需要对创建的线程做好表示,便于后续问题分析;而拒绝策略应考虑到实际业务,返回相应的提示或者友好地跳转。

看下我根据书上说明写的:

    public ExecutorService service = null;

    public TestExecutorsPool() {
        service = new ThreadPoolExecutor(1,
                1,
                60L,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<Runnable>(1),
                new TestThreadFactory("TEST"),
                new TestRejectHandler());
    }

    public static class TestThreadFactory implements ThreadFactory{
        private final String namePrefix;
        private final AtomicInteger nextId = new AtomicInteger(1);

        public TestThreadFactory(String whatFeatureOfGroup) {
            this.namePrefix = "TestThreadFactory: " + whatFeatureOfGroup + " - ";
        }

        @Override
        public Thread newThread(Runnable r) {
            String name = namePrefix + nextId.getAndIncrement();
            return new Thread(r,name);
        }
    }

    public static class TestRejectHandler implements RejectedExecutionHandler{

        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
            // 根据业务需求,采取不同的拒绝策略,通常就是打打LOG
        }
    }

再简单说下JDK提供的几个拒绝策略:

  • AbortPolicy(默认):丢弃任务并抛出异常
  • DiscardPolicy:仅丢弃任务,啥都不做
  • DiscardOldestPolicy:抛弃队列里等待最久的任务,然后把当前任务入队
  • CallerRunsPolicy:调用任务的run()方法绕过线程池直接执行,这个会在线程池被使用的线程里执行,如果是UI线程,那就要小心了。
总结:为业务定制线程池是我以前没考虑过的,以前就是拿着就用,现在想来很多场景其实都不太合适,有时更是会有不少的资源浪费(大量使用newFixedThreadPool而不释放)。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335