线程池之 ThreadPoolExecutor + 面试题

线程池介绍

线程池(Thread Pool):把一个或多个线程通过统一的方式进行调度和重复使用的技术,避免了因为线程过多而带来使用上的开销。

为什么要使用线程池?

  • 可重复使用已有线程,避免对象创建、消亡和过度切换的性能开销。
  • 避免创建大量同类线程所导致的资源过度竞争和内存溢出的问题。
  • 支持更多功能,比如延迟任务线程池(newScheduledThreadPool)和缓存线程池(newCachedThreadPool)等。

线程池使用

创建线程池有两种方式:ThreadPoolExecutor 和 Executors,其中 Executors 又可以创建 6 种不同的线程池类型,会在下节讲,本节重点来看看 ThreadPoolExecutor 的使用。

ThreadPoolExecutor 的使用

线程池使用代码如下:

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 10, 10L, TimeUnit.SECONDS, new LinkedBlockingQueue(100));
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
// 执行线程池
System.out.println("Hello, Java.");
}
});

以上程序执行结果如下:

Hello, Java.

ThreadPoolExecutor 参数说明

ThreadPoolExecutor 构造方法有以下四个,如下图所示:

其中最后一个构造方法有 7 个构造参数,包含了前三个方法的构造参数,这 7 个参数名称如下所示:

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

其代表的含义如下:

① corePoolSize

线程池中的核心线程数,默认情况下核心线程一直存活在线程池中,如果将 ThreadPoolExecutor 的 allowCoreThreadTimeOut 属性设为 true,如果线程池一直闲置并超过了 keepAliveTime 所指定的时间,核心线程就会被终止。

② maximumPoolSize

最大线程数,当线程不够时能够创建的最大线程数。

③ keepAliveTime

线程池的闲置超时时间,默认情况下对非核心线程生效,如果闲置时间超过这个时间,非核心线程就会被回收。如果 ThreadPoolExecutor 的 allowCoreThreadTimeOut 设为 true 的时候,核心线程如果超过闲置时长也会被回收。

④ unit

配合 keepAliveTime 使用,用来标识 keepAliveTime 的时间单位。

⑤ workQueue

线程池中的任务队列,使用 execute() 或 submit() 方法提交的任务都会存储在此队列中。

⑥ threadFactory

为线程池提供创建新线程的线程工厂。

⑦ rejectedExecutionHandler

线程池任务队列超过最大值之后的拒绝策略,RejectedExecutionHandler 是一个接口,里面只有一个 rejectedExecution 方法,可在此方法内添加任务超出最大值的事件处理。ThreadPoolExecutor 也提供了 4 种默认的拒绝策略:

  • new ThreadPoolExecutor.DiscardPolicy():丢弃掉该任务,不进行处理
  • new ThreadPoolExecutor.DiscardOldestPolicy():丢弃队列里最近的一个任务,并执行当前任务
  • new ThreadPoolExecutor.AbortPolicy():直接抛出 RejectedExecutionException 异常
  • new ThreadPoolExecutor.CallerRunsPolicy():既不抛弃任务也不抛出异常,直接使用主线程来执行此任务

包含所有参数的 ThreadPoolExecutor 使用代码:

public class ThreadPoolExecutorTest {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 1,
10L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(2),
new MyThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
threadPool.allowCoreThreadTimeOut(true);
for (int i = 0; i < 10; i++) {
threadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
}
class MyThreadFactory implements ThreadFactory {
private AtomicInteger count = new AtomicInteger(0);
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
String threadName = "MyThread" + count.addAndGet(1);
t.setName(threadName);
return t;
}
}

线程池执行方法 execute() VS submit()

execute() 和 submit() 都是用来执行线程池的,区别在于 submit() 方法可以接收线程池执行的返回值。

下面分别来看两个方法的具体使用和区别:

// 创建线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 10, 10L, TimeUnit.SECONDS, new LinkedBlockingQueue(100));
// execute 使用
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println("Hello, Java.");
}
});
// submit 使用
Future<String> future = threadPoolExecutor.submit(new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println("Hello, 老王.");
return "Success";
}
});
System.out.println(future.get());

以上程序执行结果如下:

Hello, Java.

Hello, 老王.

Success

线程池关闭

线程池关闭,可以使用 shutdown() 或 shutdownNow() 方法,它们的区别是:

  • shutdown():不会立即终止线程池,而是要等所有任务队列中的任务都执行完后才会终止。执行完 shutdown 方法之后,线程池就不会再接受新任务了。
  • shutdownNow():执行该方法,线程池的状态立刻变成 STOP 状态,并试图停止所有正在执行的线程,不再处理还在池队列中等待的任务,执行此方法会返回未执行的任务。

下面用代码来模拟 shutdown() 之后,给线程池添加任务,代码如下:

threadPoolExecutor.execute(() -> {
for (int i = 0; i < 2; i++) {
System.out.println("I'm " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
}
});
threadPoolExecutor.shutdown();
threadPoolExecutor.execute(() -> {
System.out.println("I'm Java.");
});

以上程序执行结果如下:

I'm 0

Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.interview.chapter5.Section2$$Lambda$2/1828972342@568db2f2 rejected from java.util.concurrent.ThreadPoolExecutor@378bf509[Shutting down, pool size = 1, active threads = 1, queued tasks = 0, completed tasks = 0]

I'm 1

可以看出,shutdown() 之后就不会再接受新的任务了,不过之前的任务会被执行完成。

相关面试题

1.ThreadPoolExecutor 有哪些常用的方法?

答:常用方法如下所示:

  • submit()/execute():执行线程池
  • shutdown()/shutdownNow():终止线程池
  • isShutdown():判断线程是否终止
  • getActiveCount():正在运行的线程数
  • getCorePoolSize():获取核心线程数
  • getMaximumPoolSize():获取最大线程数
  • getQueue():获取线程池中的任务队列
  • allowCoreThreadTimeOut(boolean):设置空闲时是否回收核心线程

2.以下程序执行的结果是什么?

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 10, 10L, TimeUnit.SECONDS, new LinkedBlockingQueue());
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 2; i++) {
System.out.println("I:" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
threadPoolExecutor.shutdownNow();
System.out.println("Java");

答:程序执行的结果是:

I:0

Java

java.lang.InterruptedException: sleep interrupted(报错信息)

I:1

题目解析:因为程序中使用了 shutdownNow() 会导致程序执行一次之后报错,抛出 sleep interrupted 异常,又因为本身有 try/catch,所以程序会继续执行打印 I:1

3.在 ThreadPool 中 submit() 和 execute() 有什么区别?

答:submit() 和 execute() 都是用来执行线程池的,只不过使用 execute() 执行线程池不能有返回方法,而使用 submit() 可以使用 Future 接收线程池执行的返回值。

submit() 方法源码(JDK 8)如下:

public <T> Future\<T\> submit(Callable\<T\> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}

execute() 源码(JDK 8)如下:

public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
//..... 其他
}

4.说一下 ThreadPoolExecutor 都需要哪些参数?

答:ThreadPoolExecutor 最多包含以下七个参数:

  • corePoolSize:线程池中的核心线程数
  • maximumPoolSize:线程池中最大线程数
  • keepAliveTime:闲置超时时间
  • unit:keepAliveTime 超时时间的单位(时/分/秒等)
  • workQueue:线程池中的任务队列
  • threadFactory:为线程池提供创建新线程的线程工厂
  • rejectedExecutionHandler:线程池任务队列超过最大值之后的拒绝策略

更多详细介绍,请见正文。

5.在线程池中 shutdownNow() 和 shutdown() 有什么区别?

答:shutdownNow() 和 shutdown() 都是用来终止线程池的,它们的区别是,使用 shutdown() 程序不会报错,也不会立即终止线程,它会等待线程池中的缓存任务执行完之后再退出,执行了 shutdown() 之后就不能给线程池添加新任务了;shutdownNow() 会试图立马停止任务,如果线程池中还有缓存任务正在执行,则会抛出 java.lang.InterruptedException: sleep interrupted 异常。

6.说一说线程池的工作原理?

答:当线程池中有任务需要执行时,线程池会判断如果线程数量没有超过核心数量就会新建线程池进行任务执行,如果线程池中的线程数量已经超过核心线程数,这时候任务就会被放入任务队列中排队等待执行;如果任务队列超过最大队列数,并且线程池没有达到最大线程数,就会新建线程来执行任务;如果超过了最大线程数,就会执行拒绝执行策略。

7.以下线程名称被打印了几次?

ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 1,
10L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(2),
new ThreadPoolExecutor.DiscardPolicy());
threadPool.allowCoreThreadTimeOut(true);
for (int i = 0; i < 10; i++) {
threadPool.execute(new Runnable() {
@Override
public void run() {
// 打印线程名称
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});

答:线程名被打印了 3 次。

题目解析:线程池第 1 次执行任务时,会新创建任务并执行;第 2 次执行任务时,因为没有空闲线程所以会把任务放入队列;第 3 次同样把任务放入队列,因为队列最多可以放两条数据,所以第 4 次之后的执行都会被舍弃(没有定义拒绝策略),于是就打印了 3 次线程名称。

总结

ThreadPoolExecutor 是创建线程池最传统和最推荐使用的方式,创建时要设置线程池的核心线程数和最大线程数还有任务队列集合,如果任务量大于队列的最大长度,线程池会先判断当前线程数量是否已经到达最大线程数,如果没有达到最大线程数就新建线程来执行任务,如果已经达到最大线程数,就会执行拒绝策略(拒绝策略可自行定义)。线程池可通过 submit() 来调用执行,从而获得线程执行的结果,也可以通过 shutdown() 来终止线程池。


欢迎关注我的公众号,回复关键字“Java” ,将会有大礼相送!!! 祝各位面试成功!!!



%97%E5%8F%B7%E4%BA%8C%E7%BB%B4%E7%A0%81.png)

线程池之 ThreadPoolExecutor的更多相关文章

  1. 从源码解读线程(Thread)和线程池(ThreadPoolExecutor)的状态

    线程是比进程更加轻量级的调度执行单位,理解线程是理解并发编程的不可或缺的一部分:而生产过程中不可能永远使用裸线程,需要线程池技术,线程池是管理和调度线程的资源池.因为前不久遇到了一个关于线程状态的问题 ...

  2. Java并发编程:Java线程池核心ThreadPoolExecutor的使用和原理分析

    目录 引出线程池 Executor框架 ThreadPoolExecutor详解 构造函数 重要的变量 线程池执行流程 任务队列workQueue 任务拒绝策略 线程池的关闭 ThreadPoolEx ...

  3. 线程池 一 ThreadPoolExecutor

    java.util.concurrent public class ThreadPoolExecutor extends AbstractExecutorService ThreadPoolExecu ...

  4. 高并发之——不得不说的线程池与ThreadPoolExecutor类浅析

    一.抛砖引玉 既然Java中支持以多线程的方式来执行相应的任务,但为什么在JDK1.5中又提供了线程池技术呢?这个问题大家自行脑补,多动脑,肯定没坏处,哈哈哈... 说起Java中的线程池技术,在很多 ...

  5. 【高并发】不得不说的线程池与ThreadPoolExecutor类浅析

    大家好,我是冰河~~ 今天,我们一起来简单聊聊线程池中的ThreadPoolExecutor类,好了,不多说了,开始进入今天的正题. 一.抛砖引玉 既然Java中支持以多线程的方式来执行相应的任务,但 ...

  6. 线程池:ThreadPoolExecutor

    [ThreadPoolExecutor的使用和思考]   public ThreadPoolExecutor(int corePoolSize,                             ...

  7. Java线程池之ThreadPoolExecutor

    前言 线程池可以提高程序的并发性能(当然是合适的情况下),因为对于没有线程的情况下,我们每一次提交任务都新建一个线程,这种方法存在不少缺陷: 1.  线程的创建和销毁的开销非常高,线程的创建需要时间, ...

  8. 从源码看JDK提供的线程池(ThreadPoolExecutor)

    一丶什么是线程池 (1)博主在听到线程池三个字的时候第一个想法就是数据库连接池,回忆一下,我们在学JavaWeb的时候怎么理解数据库连接池的,数据库创建连接和关闭连接是一个比较耗费资源的事情,对于那些 ...

  9. java并发线程池---了解ThreadPoolExecutor就够了

    总结:线程池的特点是,在线程的数量=corePoolSize后,仅任务队列满了之后,才会从任务队列中取出一个任务,然后构造一个新的线程,循环往复直到线程数量达到maximumPoolSize执行拒绝策 ...

随机推荐

  1. python之子类继承父类时进行初始化的一些问题

    直接看代码: class Person: def __init__(self): self.name = "jack" class Student(Person): def __i ...

  2. 20191024-3 互评Alpha阶段作品——扛把子组

    此作业要求参见:https://edu.cnblogs.com/campus/nenu/2019fall/homework/9860 基于NABCD评论作品,及改进建议 1.根据(不限于)NABCD评 ...

  3. $[TJOI2017]$ 可乐 矩阵优化$dp$

    \(Sol\) 设\(f_i\)为到第\(i\)秒的方案数,显然\(f_i=\)在第\(i\)秒前爆炸的方案数+在第\(i\)秒爆炸的方案数+在第\(i\)秒停下的方案数+在第\(i\)秒走向下一个城 ...

  4. centos下MySQL登录1045问题解决

    由于需要在centos下部署整个应用,自然少不了对数据库的操作.但很多人可能会遇到一些问题,比如创建用户成功,但是却无法登录. 无法登陆一般就两个原因.第一,远程访问端口没开,第二个原因就是密码错误了 ...

  5. 【记】创建 VirtualBoxClient COM 对象失败. 应用程序将被中断

    1. 在本地64位win7系统安装VirtualBox完,启动时提示错误 原因:兼容性造成的 按照下图显示修改VirtualBox快捷方式的兼容性 2. 启动虚拟机时,提示 点击弹出框的确定按钮后,接 ...

  6. 【转】ArcGIS Server 10.1 动态图层

    ArcGISServer将GIS资源以服务的方式发布,能够让更多的人在Web上浏览.使用.不过,诸如气象.环保等方面的信息是实时变化的,按照之前常规的方法,我们先要将最新获得的信息组织成地图文档后再对 ...

  7. Shell脚本传递带有空格的参数

    在另一博文<Shell脚本实现DB2数据库表导出到文件>中实现了通过脚本实现将DB2数据库导出到文件,需要传入七个参数,最后一个是一个带有空格字符串,所以传入的时候有点问题,会自动识别空格 ...

  8. TensorFlow——批量归一化操作

    批量归一化 在对神经网络的优化方法中,有一种使用十分广泛的方法——批量归一化,使得神经网络的识别准确度得到了极大的提升. 在网络的前向计算过程中,当输出的数据不再同一分布时,可能会使得loss的值非常 ...

  9. double涉及大数据的时候会变成科学计数法

    double b=1.23456789128E8DecimalFormat df = new DecimalFormat("0.00");//精度自己控制保留几位小数点 Strin ...

  10. spring boot 的中文乱码

    首先 自检IDEA的编码 配置文件加入设置http tomcat spring.http.encoding.force=true spring.http.encoding.charset=UTF-8 ...