ThreadPool学习

概述

重新学习一下线程池

参考文章:

https://www.jianshu.com/p/0e4a5e70bf0e

简介

工作原理
核心参数

线程池中有6个核心参数,如下:

  • 上述6个参数的配置 决定了 线程池的功能,具体设置时机 —-> 创建 线程池类对象时 传入
  1. ThreadPoolExecutor类 = 线程池的真正实现类
  2. 开发者可根据不同需求 配置核心参数,从而实现自定义线程池
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 创建线程池对象如下
// 通过 构造方法 配置核心参数
Executor executor = new ThreadPoolExecutor(
CORE_POOL_SIZE,
MAXIMUM_POOL_SIZE,
KEEP_ALIVE,
TimeUnit.SECONDS,
sPoolWorkQueue,
sThreadFactory
);

// 构造函数源码分析
public ThreadPoolExecutor (int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable workQueue>,
ThreadFactory threadFactory )

同时,Java 里已内置4种常用的线程池(即 已经配置好核心参数),下面慢慢学。

工作逻辑

使用

流程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 1. 创建线程池
// 创建时,通过配置线程池的参数,从而实现自己所需的线程池
Executor threadPool = new ThreadPoolExecutor(
CORE_POOL_SIZE,
MAXIMUM_POOL_SIZE,
KEEP_ALIVE,
TimeUnit.SECONDS,
sPoolWorkQueue,
sThreadFactory
);
// 注:在Java中,已内置4种常见线程池,下面会详细说明

// 2. 向线程池提交任务:execute()
// 说明:传入 Runnable对象
threadPool.execute(new Runnable() {
@Override
public void run() {
... // 线程执行任务
}
});

// 3. 关闭线程池shutdown()
threadPool.shutdown();

// 关闭线程的原理
// a. 遍历线程池中的所有工作线程
// b. 逐个调用线程的interrupt()中断线程(注:无法响应中断的任务可能永远无法终止)

// 也可调用shutdownNow()关闭线程:threadPool.shutdownNow()
// 二者区别:看下面
// 使用建议:一般调用shutdown()关闭线程池;若任务不一定要执行完,则调用shutdownNow()

shutdown()

  • 线程池的状态则立刻变成SHUTDOWN状态

  • 不能接受新的submit(即不能再往线程池内添加任何任务,否则将抛出异常)

  • 并没有任何的interrupt操作,会等待线程池中所有线程(执行中的以及排队的)执行完毕
  • 可以理解为是个标识性质的方法,标识这程序有意愿在此刻终止线程池的后续操作。

shutdownNow()

  • 线程池的状态立刻变成STOP状态

  • 会尝试interrupt线程池中正在执行的线程

  • 等待执行的线程也会被取消
  • 但是并不能保证一定能成功的interrupt线程池中的线程。(因为它试图终止线程的方法是通过调用Thread.interrupt()方法来实现的,但是大家知道,这种方法的作用有限,如果线程中没有sleep 、wait、Condition、定时锁等应用, interrupt()方法是无法中断当前的线程的。所以,ShutdownNow()并不代表线程池就一定立即就能退出,它可能必须要等待所有正在执行的任务都执行完成了才能退出。)
  • 会返回并未终止的线程列表List

shutdownNow()方法比shutdown()强硬了很多,不仅取消了排队的线程而且确实尝试终止当前正在执行的线程。

常见的4类功能线程池

根据参数的不同配置,Java中最常见的线程池有4类:

  • 定长线程池(FixedThreadPool
  • 定时线程池(ScheduledThreadPool
  • 可缓存线程池(CachedThreadPool
  • 单线程化线程池(SingleThreadExecutor

即 对于上述4类线程池,Java已根据 应用场景 配置好核心参数

定长线程池(FixedThreadPool)

特点:只有核心线程 & 不会被回收、线程数量固定、任务队列无大小限制(超出的线程任务会在队列中等待)

应用场景:控制线程最大并发数

具体使用:通过 Executors.newFixedThreadPool() 创建

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 1. 创建定长线程池对象 & 设置线程池线程数量固定为3
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);

// 2. 创建好Runnable类线程对象 & 需执行的任务
Runnable task =new Runnable(){
public void run(){
System.out.println("执行任务啦");
}
};

// 3. 向线程池提交任务:execute()
fixedThreadPool.execute(task);

// 4. 关闭线程池
fixedThreadPool.shutdown();
定时线程池(ScheduledThreadPool )
  • 特点:核心线程数量固定、非核心线程数量无限制(闲置时马上回收)
  • 应用场景:执行定时 / 周期性 任务
  • 使用:通过Executors.newScheduledThreadPool()创建
  • 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 1. 创建 定时线程池对象 & 设置线程池线程数量固定为5
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);

// 2. 创建好Runnable类线程对象 & 需执行的任务
Runnable task =new Runnable(){
public void run(){
System.out.println("执行任务啦");
}
};
// 3. 向线程池提交任务:schedule()
scheduledThreadPool.schedule(task, 1, TimeUnit.SECONDS); // 延迟1s后执行任务
scheduledThreadPool.scheduleAtFixedRate(task,10,1000,TimeUnit.MILLISECONDS);// 延迟10ms后、每隔1000ms执行任务

// 4. 关闭线程池
scheduledThreadPool.shutdown();
可缓存线程池(CachedThreadPool)
  • 特点:只有非核心线程、线程数量不固定(可无限大)、灵活回收空闲线程(具备超时机制,全部回收时几乎不占系统资源)、新建线程(无线程可用时)
  • 任何线程任务到来都会立刻执行,不需要等待
  • 应用场景:执行大量、耗时少的线程任务
  • 使用:通过Executors.newCachedThreadPool()创建
  • 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 1. 创建可缓存线程池对象
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

// 2. 创建好Runnable类线程对象 & 需执行的任务
Runnable task =new Runnable(){
public void run(){
System.out.println("执行任务啦");
}
};

// 3. 向线程池提交任务:execute()
cachedThreadPool.execute(task);

// 4. 关闭线程池
cachedThreadPool.shutdown();

//当执行第二个任务时若第一个任务已经完成
//那么会复用执行第一个任务的线程,而不用每次新建线程。
单线程化线程池(SingleThreadExecutor)

特点:只有一个核心线程(保证所有任务按照指定顺序在一个线程中执行,不需要处理线程同步的问题)

应用场景:不适合并发但可能引起IO阻塞性及影响UI线程响应的操作,如数据库操作,文件操作等

使用:通过Executors.newSingleThreadExecutor()创建

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 1. 创建单线程化线程池
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();

// 2. 创建好Runnable类线程对象 & 需执行的任务
Runnable task =new Runnable(){
public void run(){
System.out.println("执行任务啦");
}
};

// 3. 向线程池提交任务:execute()
singleThreadExecutor.execute(task);

// 4. 关闭线程池
singleThreadExecutor.shutdown();
总结&对比

说明

阿里的开发规范中有这么一条:

线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,避免资源耗尽的风险。

因此,我们可以通过ThreadPoolExecutor的方式自己创建线程池,根据业务逻辑选择阻塞队列、拒绝策略等。

0%