了解线程池
为什么使用线程池?
- 减少线程创建销毁的次数;
- 统一管理线程;
- 调整参数合理利用CPU和内存;
1 线程池基本使用
1.1 线程池参数
线程池参数包含:核心线程数
、最大线程数
、存活时间
、存活时间单位
、任务的存储队列
、线程工厂
、拒绝策略
1.2 工作队列和自定义线程工厂、拒绝策略
多线程中常用的队列是线程安全的BlockingQueue,它的子类有ArrayBlockingQueue
、LinkedBlockingQueue
、SynchronousQueue
、PriorityBlockingQueue
上面的队列操作函数:有对于不能立即满足但可能在将来某一时刻可以满足的操作,有以下四种不同形式的处理方式:
- 抛出一个异常
- 返回一个特殊值(null 或 false,具体取决于操作)
- 在操作可以成功前,无限期地阻塞当前线程
- 在放弃前只在给定的最大时间限制内阻塞
自定义线程工厂:
package com.maokeyang.transactional.thread;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
public class MyThreadFactory 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;
public MyThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
//此处只修改了一下名字前缀
namePrefix = "自定义-" +
poolNumber.getAndIncrement() +
"-thread-";
}
@Override
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);
}
// 异常处理的一种方式
t.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("线程:" + t + ", 异常:" + e);
}
});
return t;
}
}
自定义拒绝策略:
public class MyRejectHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("这个是我的拒绝策略");
}
}
使用测试类:
public class ThreadPoolExecutorTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = new ThreadPoolExecutor(
1,
1,
0,
TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(1),
new MyThreadFactory(), new MyRejectHandler());
executorService.execute(new Task());
executorService.execute(new Task());
executorService.execute(new Task());
}
}
public class Task implements Runnable {
@Override
public void run() {
System.out.println("进入task方法...");
int i = 1/0;
}
}
运行结果:
2 异常如何处理?
线程池的提交方式一共两种 submit
和 execute
public class ThreadPoolException {
public static void main(String[] args) {
//创建一个线程池
ExecutorService executorService= Executors.newFixedThreadPool(1);
//当线程池抛出异常后 submit无提示,其他线程继续执行
executorService.submit(new Task());
//当线程池抛出异常后 execute抛出异常,其他线程继续执行新任务
executorService.execute(new Task());
}
}
//任务类
class Task implements Runnable{
@Override
public void run() {
System.out.println("进入了task方法!!!");
int i=1/0;
}
}
运行结果:
可以看到: submit 不打印异常信息,而execute则会打印异常信息!submit的方式不打印异常信息,显然在生产中,是不可行的,因为我们无法保证线程中的任务永不异常,而如果使用submit的方式出现了异常,直接如上写法,我们将无法获取到异常信息,做出对应的判断和处理,所以下一步需要知道如何获取线程池抛出的异常!
submit()想要获取异常信息就必须使用get()方法!!
//当线程池抛出异常后 submit无提示,其他线程继续执行
Future<?> submit = executorService.submit(new task());
submit.get();
如何处理异常呢?
- run方法里使用
try - catch
- 使用
Thread.setDefaultUncaughtExceptionHandler
方法捕获异常
方案一中,每一个任务都要加一个try-catch 实在是太麻烦了,而且代码也不好看,那么这样想的话,可以用Thread.setDefaultUncaughtExceptionHandler方法捕获异常。
应用在线程池中如下所示:重写它的线程工厂方法
,在线程工厂创建线程的时候,针对每一个新建的Thread
对象都赋予UncaughtExceptionHandler处理器对象。处理方式:自己顶一个实现UncaughtExceptionHandler
的类,创建此类对象,对Thread对象进行设置。 - 重写
ThreadPoolExecutor
类的afterExecute(Runnable r, Throwable t)
方法进行异常处理
3 线程池钩子(beforeExecute && afterExecute)
通过这两钩子可以实现线程执行前逻辑、执行中异常打印
public class ThreadPoolExecutorTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = new ThreadPoolExecutor(
1,1,0,
TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(1),
new MyThreadFactory(), new MyRejectHandler()) {
@Override
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
System.out.println("Thread beforeExecute: " + t);
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
System.out.println("afterExecute里面获取到异常信息,处理异常" + t.getMessage());
}
};
executorService.execute(new Task());
}
}
4 线程池停止
shutdown()
等待正在执行和队列中的任务执行完毕,才关闭。 但是添加新任务会直接拒绝
可以根据isShutDown()
判断TRUE 或者FALSE ; 另外isTerminated()
判断所有任务是否执行完毕shutdownNow()
: interrupt 当前执行的任务,不再添加新任务,返回等待的任务
5 线程池实现原理、源码分析
线程池运行状态包含:
- RUNNING:接收新的任务并对任务队列里的任务进行处理;
- SHUTDOWN:不再接收新的任务,但是会对任务队列中的任务进行处理;
- STOP:不接收新任务,也不再对任务队列中的任务进行处理,并中断正在处理的任务;
- TIDYING:所有任务都已终止,线程数为0,在转向TIDYING状态的过程中,线程会执行
terminated()
钩子方法,钩子方法是指在本类中是空方法,而在子类中进行具体实现的方法; - TERMINATED:
terminated()
方法执行结束后会进入这一状态,表示线程池已关闭。
线程池运行状态存储在AtomicInteger类型的变量ctl的最高三位中,因此各种状态所对应的整型变量的二进制格式除了最高三位,其余都是0 (如代码注释所示)。这些变量的操作会涉及到一些位运算和原子操作,现在只需要了解这些状态变量从小到大的顺序是RUNNING<SHUTDOWN<STOP<TIDYING<TERMINATED,另外很重要的一点就是这些状态的转化只能从小到大,不能从大到小。它们在源码中对应的变量如下:
// RUNNING: 十进制:-536870912 二进制:11100000000000000000000000000000
private static final int RUNNING = -1 << COUNT_BITS;
// SHUTDOWN: 十进制:0 二进制:0
private static final int SHUTDOWN = 0 << COUNT_BITS;
// STOP: 十进制:536870912 二进制:00100000000000000000000000000000
private static final int STOP = 1 << COUNT_BITS;
// TIDYING: 十进制:1073741824 二进制:01000000000000000000000000000000
private static final int TIDYING = 2 << COUNT_BITS;
// TERMINATED: 十进制:1610612736 二进制:01100000000000000000000000000000
private static final int TERMINATED = 3 << COUNT_BITS;
// COUNT_BITS: 29
private static final int COUNT_BITS = Integer.SIZE - 3;
线程池运行状态关系图
线程池的状态存储在一个 AtomicInteger 类型的变量 ctl 中,其实这个变量还存储了另一个线程池的重要信息,那就是线程数量 (workerCount)。从名字就可以看出,AtomicInteger 类对 int 类型进行了封装,可以对整数进行各种原子操作,适合在多线程环境中使用。大家都知道 int 类型有32位,而线程池的运行状态一共有5种,所以 ctl 的高三位足以表示所有的线程池运行状态 (23 = 8 > 5)。而另一个变量 workerCount 正好存储在其低29位中 (取值范围是0到229-1)。ThreadPoolExecutor 的源码中有很多对 ctl 变量的操作,在这里先把他们搞懂,后面就不会懵逼了。
// 使用一个AtomicInteger类型的变量ctl同时控制线程池运行状态和线程数量,初始运行状态
// 为RUNNING,线程数为0
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// CAPACITY: 十进制: 536870911 二进制: 00011111111111111111111111111111
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// 获取线程池当前状态,CAPACITY取反后高三位都是1,低29位都是0,再和c进行按位与运算,即可获得runState变量
private static int runStateOf(int c) { return c & ~CAPACITY; }
// CAPACITY高三位都是0,低29位都是0,和c进行按位与运算即可获得workerCount变量
private static int workerCountOf(int c) { return c & CAPACITY; }
// 初始化ctl变量,runState和workerCount进行按位或运算即可将这两个变量存储在一个变量中
private static int ctlOf(int rs, int wc) { return rs | wc; }
// 部分方法
// 判断线程池当前运行状态是否小于给定值
private static boolean runStateLessThan(int c, int s) {
return c < s;
}
// 判断线程池当前运行状态是否大于等于给定值
private static boolean runStateAtLeast(int c, int s) {
return c >= s;
}
// 判断线程池是否处于RUNNING状态
private static boolean isRunning(int c) {
return c < SHUTDOWN;
}
5.1 线程池实现任务复用的原理
相同线程执行不同的任务,对应源码: