带着问题

  1. 阿里Java代码规范为什么不允许使用Executors快速创建线程池?
  2. 下面的代码输出是什么?
ThreadPoolExecutor executor = new ThreadPoolExecutor(
1, //corePoolSize
100, //maximumPoolSize
100, //keepAliveTime
TimeUnit.SECONDS, //unit
new LinkedBlockingDeque<>(100));//workQueue for (int i = 0; i < 5; i++) {
final int taskIndex = i;
executor.execute(() -> {
System.out.println(taskIndex);
try {
Thread.sleep(Long.MAX_VALUE);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}

A) 0 1 2 3 4 5

B) 0~5 顺序不一致输出5行

C) 0

基础

什么是线程池?

线程池可以通过池看出来是一个资源集,任何池的作用都大同小异,主要是用来减少资源创建、初始化的系统开销。

创建线程很“贵”吗?

是的。创建线程的代价是昂贵的。

我们都知道系统中的每个进程有自己独立的内存空间,而被称为轻量级进程的线程也是需要的。

在JVM中默认一个线程需要使用256k~1M(取决于32位还是64位操作系统)的内存。(具体的数组我们不深究,因为随着JVM版本的变化这个默认值随时可能发生变更,我们只需要知道线程是需要占用内存的)

除了内存还有更多吗?

许多文章会将上下文切换、CPU调度列入其中,这边不将线程调度列入是因为睡眠中的线程不会被调度(OS控制),如果不是睡眠中的线程那么是一定需要被调度的。

但在JVM中除了创建时的内存消耗,还会给GC带来压力,如果频繁创建线程那么相对的GC的时候也需要回收对应的线程。

线程池的机制?

可以看到线程池是一种重复利用线程的技术,线程池的主要机制就是保留一定的线程数在没有事情做的时候使之睡眠,当有活干的时候拿一个线程去运行。

这些牵扯到线程池实现的具体策略。

还有哪些常见的池?

  • 线程池
  • 连接池(数据库连接、TCP连接等)
  • BufferPool
  • ......

Java中的线程池

UML图(Java 8)



可以看到真正的实现类有

  1. ThreadPoolExecutor (1.5)
  2. ForkJoinPool (1.7)
  3. ScheduledThreadPoolExecutor (1.5)

今天我们主要谈谈 ThreadPoolExecutor 也是使用率较高的一个实现。

Executors提供的工厂方法

  1. newCachedThreadPool (ThreadPoolExecutor)

    创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

  2. newFixedThreadPool (ThreadPoolExecutor)

    创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

  3. newSingleThreadExecutor (ThreadPoolExecutor)

    创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

  4. newScheduledThreadPool (ScheduledThreadPoolExecutor)

    创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

  5. newSingleThreadScheduledExecutor (ScheduledThreadPoolExecutor)

    创建一个单线程用于定时以及周期性执行任务的需求。

  6. newWorkStealingPool (1.8 ForkJoinPool)

    创建一个工作窃取

可以看到各种不同的工厂方法中使用的线程池实现类最终只有3个,对应关系如下:

工厂方法 实现类
newCachedThreadPool ThreadPoolExecutor
newFixedThreadPool ThreadPoolExecutor
newSingleThreadExecutor ThreadPoolExecutor
newScheduledThreadPool ScheduledThreadPoolExecutor
newSingleThreadScheduledExecutor ScheduledThreadPoolExecutor
newWorkStealingPool ForkJoinPool

ThreadPoolExecutor

首先我们看下 ThreadPoolExecutor 的完全构造函数

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

    核心池大小,除非设置了 allowCoreThreadTimeOut 否则哪怕线程超过空闲时间,池中也要最少要保留这个数目的线程。

    需要注意的是,corePoolSize所需的线程并不是立即创建的,需要在提交任务之后进行创建,所以如果有大量的缓存线程数可以先提交一个空任务让线程池将线程先创建出来,从而提升后续的执行效率。

  2. maximumPoolSize

    允许的最大线程数。

  3. keepAliveTime

    空闲线程空闲存活时间,核心线程需要 allowCoreThreadTimeOut 为true才会退出。

  4. unit

    keepAliveTime 配合,设置 keepAliveTime 的单位,如:毫秒、秒。

  5. workQueue

    线程池中的任务队列。上面提到线程池的主要作用是复用线程来处理任务,所以我们需要一个队列来存放需要执行的任务,在使用池中的线程来处理这些任务,所以我们需要一个任务队列。

  6. threadFactory

    当线程池判断需要新的线程时通过线程工程创建线程。

  7. handler

    执行被阻止时的处理程序,线程池无法处理。这个与任务队列相关,比如队列中可以指定队列大小,如果超过了这个大小该怎么办呢?JDK已经为我们考虑到了,并提供了4个默认实现。

    下列是JDK中默认携带的策略:

    1. AbortPolicy (默认)

    抛出 RejectedExecutionException 异常。

    1. CallerRunsPolicy

    调用当前线程池所在的线程去执行。

    1. DiscardPolicy

    直接丢弃当前任务。

    1. DiscardOldestPolicy

    将最旧的任务丢弃,将当前任务添加到队列。

容易混淆的参数:corePoolSize maximumPoolSize workQueue

任务队列、核心线程数、最大线程数的逻辑关系

  1. 当线程数小于核心线程数时,创建线程。
  2. 当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
  3. 当线程数大于等于核心线程数,且任务队列已满
    1. 若线程数小于最大线程数,创建线程
    2. 若线程数等于最大线程数,调用拒绝执行处理程序(默认效果为:抛出异常,拒绝任务)

那么这三个参数推荐如何设置,有最优值吗?

由于java对于协程的支持不友好,所以会大量依赖于线程池和线程。

从而这个值没有最优推荐,需要根据业务需求情况来进行设置。

不同的需求类型可以创建多个不同的线程池来执行。

问题1:阿里开发规范为什么不允许Executors快速创建线程池?

参考地址:https://github.com/alibaba/p3c

可以看到原因很简单

  1. newSingleThreadExecutor
  2. newFixedThreadPool

workQueue 参数直接 使用了 new LinkedBlockingQueue<Runnable>() 理论上可以无限添加任务到线程池。

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

如果提交到线程池的任务由问题,比如 sleep 永久,会造成内存泄漏,最终导致OOM。

同时 阿里还推荐自定义 threadFactory 设置线程名称便于以后排查问题。

问题2:下面的代码输出是什么?

应该选C。

虽然最大线程数有100但核心线程数为1,任务队列由100。

满足了 '当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。' 这个条件。

所以后续添加的任务都会被堵塞。

最后

关于 ThreadPoolExecutor 的逻辑在实际使用的时候会有点奇怪,因为线程池中的线程并没有超过最大线程数,有没有一种可能当任务被堵塞很久的时候创建新的线程池来处理呢?

这边推荐大家使用 newWorkStealingPool,也就是ForkJoinPool。采取了工作窃取的模式。

后续会跟大家一起聊聊 ForkJoinPool。

java线程池,阿里为什么不允许使用Executors?的更多相关文章

  1. 【Java 多线程】Java线程池类ThreadPoolExecutor、ScheduledThreadPoolExecutor及Executors工厂类

    Java中的线程池类有两个,分别是:ThreadPoolExecutor和ScheduledThreadPoolExecutor,这两个类都继承自ExecutorService.利用这两个类,可以创建 ...

  2. java线程池,工作窃取算法

    前言 在上一篇<java线程池,阿里为什么不允许使用Executors?>中我们谈及了线程池,同时又发现一个现象,当最大线程数还没有满的时候耗时的任务全部堆积给了单个线程, 代码如下: T ...

  3. java 线程池 ExeutorService

    Java线程池 ExecutorService 原文:https://blog.csdn.net/suifeng3051/article/details/49443835/ 本篇主要涉及到的是java ...

  4. Java线程池 ExecutorService

    一.ExecutorService介绍 ExecutorService是Java中对线程池定义的一个接口,它java.util.concurrent包中,在这个接口中定义了和后台任务执行相关的方法:  ...

  5. 干货,阿里P8浅谈对java线程池的理解(面试必备)

    线程池的概念 线程池由任务队列和工作线程组成,它可以重用线程来避免线程创建的开销,在任务过多时通过排队避免创建过多线程来减少系统资源消耗和竞争,确保任务有序完成:ThreadPoolExecutor ...

  6. 由浅入深理解Java线程池及线程池的如何使用

    前言 多线程的异步执行方式,虽然能够最大限度发挥多核计算机的计算能力,但是如果不加控制,反而会对系统造成负担.线程本身也要占用内存空间,大量的线程会占用内存资源并且可能会导致Out of Memory ...

  7. Java线程池理解及用法

    前言 多线程的异步执行方式,虽然能够最大限度发挥多核计算机的计算能力,但是如果不加控制,反而会对系统造成负担.线程本身也要占用内存空间,大量的线程会占用内存资源并且可能会导致Out of Memory ...

  8. Java线程池 ThreadPoolExecutor类

    什么是线程池? java线程池是将大量的线程集中管理的类, 包括对线程的创建, 资源的管理, 线程生命周期的管理. 当系统中存在大量的异步任务的时候就考虑使用java线程池管理所有的线程, 从而减少系 ...

  9. 新鲜出炉!JAVA线程池精华篇深度讲解,看完你还怕面试被问到吗?

    前言 前两天趁着假期在整理粉丝私信的时候看到一个粉丝朋友的私信跟我说自己现在正在复习准备面试,自己在复习到线程池这一块的时候有点卡壳,总感觉自己差了点什么.想要我帮他指导一下.这不趁着假期我也有时间我 ...

随机推荐

  1. MySQL sys Schema 简单介绍-1

    参考文档: MySQL- 5.7 sys schema笔记 MySQL 5.7新特性:SYS库详解 MySQL Performance Schema&sys Schema介绍 内存分配统计视图 ...

  2. 如何实现LRU算法?

    1.什么是LRU算法? LRU是一种缓存淘汰机制策略. 计算机的缓存容量有限,如果缓存满了就要删除一些内容,给新的内容腾位置.但是要删除哪些内容呢?我们肯定希望删掉那些没有用的缓存,而把有用的数据继续 ...

  3. JavaSE核心知识

    一:Java简介 1. Java语言的介绍:Java是一门面向对象编程语言,具有功能强大和简单易用两个特征. 2. Java语言的特点:简单性.面向对象.分布式.健壮性.安全性.平台独立与可移植性.多 ...

  4. STM32F0_HAL库驱动描述——LL驱动程序概述

    LL驱动概述 低层(LL)驱动器旨在提供快速轻量级的专家导向层,它比硬件更接近硬件: 与HAL相反,LLAPI不适用于优化访问不是关键功能的外设设备,或者需要大量软件配置和/或复杂的高级堆栈(如USB ...

  5. python爬虫常用正则表达式组合匹配

    ["\']匹配什么?(.*?)匹配什么? ["\']       -----------     匹配单双引号 (.*?)xxx ----------- 匹配任意长度字符+xxx ...

  6. Excel催化剂开源第46波-按行列排列多个图形技术要点

    此篇对应功能出自:第10波-快速排列工作表图形对象 - 简书 https://www.jianshu.com/p/eab71f2969a6 在Excel的对象模型中,列的宽度不是一般所期待的和行高一样 ...

  7. Java项目案例之---登录和修改(JSP)

    登录和修改(JSP) 通过案例学习jsp的常用知识点: 1.创建一个Map集合,用于存放学生信息,将学生信息存入Map中 2.通过page将需要的包导入 3.用request.getParameter ...

  8. opencv编译

    1. clone源码 https://github.com/opencv/opencv 2. 安装cmake 3. cmake配置的时候,输出目录需要另外设置一个目录,不可以放到源码目录 4. 用cm ...

  9. Python基础总结之第四天开始【格式化‘字符串’】(新手可相互督促)

    年薪20万... 字符串格式化: 先看小案例-------: a = 'hello,my name is %s,I like %s.'%('XiaoHong','football') #在字符串中,[ ...

  10. 05-k8s调度器、预选策略、优选函数

    目录 k8s调度器.预选策略.优选函数 节点选择过程 调度器 预选策略 优选函数 高级调度设置机制 node选择器/node亲和调度 pod亲和性 污点调度 Taints 与 Tolerations ...