用到的锁
ReentrantLock , CAS
实现关系
Excutor接口 ,只声明了一个方法execute(Runnable)
->继承
ExcutorService接口,加入了shutdown,submit,invokeAll(),invokeAny等方法
->实现
AbstractExcutorService抽象类,基本实现了ExecutorService中声明的所有方法;
->继承
ThreadPoolExcutor,ForkJoinPool
ForkJoinPoll:先将任务分解,最后再汇总
线程池构造方法
java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类,因此如果要透彻地了解Java中的线程池,必须先了解这个类。下面我们来看一下ThreadPoolExecutor类的具体实现源码。
在ThreadPoolExecutor类中提供了四个构造方法:
public class ThreadPoolExecutor extends AbstractExecutorService {
.....
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
...
}
构造器中各个参数的含义:
从上面的代码可以得知,ThreadPoolExecutor继承了AbstractExecutorService类,并提供了四个构造器,事实上,通过观察每个构造器的源码具体实现,发现前面三个构造器都是调用的第四个构造器进行的初始化工作。
-
corePoolSize:
- 核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;
-
maximumPoolSize:
- 线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;
-
keepAliveTime:
- 表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
-
unit:
- 参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:
TimeUnit.DAYS; //天
TimeUnit.HOURS; //小时
TimeUnit.MINUTES; //分钟
TimeUnit.SECONDS; //秒
TimeUnit.MILLISECONDS; //毫秒
TimeUnit.MICROSECONDS; //微妙
TimeUnit.NANOSECONDS; //纳秒
-
workQueue:
- 一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:
ArrayBlockingQueue;
LinkedBlockingQueue;
SynchronousQueue;(固定为1)
TransferQueue(固定长度)
ArrayBlockingQueue和PriorityBlockingQueue使用较少,一般使用LinkedBlockingQueue和Synchronous。线程池的排队策略与BlockingQueue有关。
-
threadFactory:
- 线程工厂,实现newThread方法,主要用来创建worker所属的线程;
-
handler:
- 表示当拒绝处理任务时的策略,有以下四种取值:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程) ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
一般会自定义策略比如把消息保存下来(尤其是订单之类的)保存到kafka,redis或数据库,日志等
自定义策略
import java.util.concurrent.*;
public class T14_MyRejectedHandler {
public static void main(String[] args) {
ExecutorService service = new ThreadPoolExecutor(4, 4,
0, TimeUnit.SECONDS, new ArrayBlockingQueue<>(6),
Executors.defaultThreadFactory(),
new MyHandler());
}
static class MyHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
//log("r rejected")
//save r kafka mysql redis
//try 3 times
if (executor.getQueue().size() < 10000) {
//try put again();
}
}
}
}
核心方法
execute()方法实际上是Executor中声明的方法,在ThreadPoolExecutor进行了具体的实现,这个方法是ThreadPoolExecutor的核心方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行。
submit()方法是在ExecutorService中声明的方法,在AbstractExecutorService就已经有了具体的实现,在ThreadPoolExecutor中并没有对其进行重写,这个方法也是用来向线程池提交任务的,但是它和execute()方法不同,它能够返回任务执行的结果,去看submit()方法的实现,会发现它实际上还是调用的execute()方法,只不过它利用了Future来获取任务执行结果(Future相关内容将在下一篇讲述)。
shutdown()和shutdownNow()是用来关闭线程池的。
还有很多其他的方法:
比如:getQueue() 、getPoolSize() 、getActiveCount()、getCompletedTaskCount()等获取与线程池相关属性的方法,有兴趣的朋友可以自行查阅API。
submit方法调用
submit是异步的,交给线程池后主线程该干嘛干嘛返回值可由future的get方法得到,该方法是阻塞的
Callable -> Runnable + ret
Future -> 存储执行的将来才会发生的结果
import java.util.concurrent.*;
public class T03_Callable {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<String> c = new Callable() {
@Override
public String call() throws Exception {
return "Hello Callable";
}
};
ExecutorService service = Executors.newCachedThreadPool();
Future<String> future = service.submit(c); //异步
System.out.println(future.get());//阻塞
service.shutdown();
}
}
扩展
Futuretask
FutureTask -> Future + Callable
/**
* 认识future
* 异步
*/
package com.mashibing.juc.c_026_01_ThreadPool;
import java.util.concurrent.*;
public class T06_00_Future {
public static void main(String[] args) throws InterruptedException, ExecutionException {
FutureTask<Integer> task = new FutureTask<>(()->{
TimeUnit.MILLISECONDS.sleep(500);
return 1000;
}); //new Callable () { Integer call();}
new Thread(task).start();
System.out.println(task.get()); //阻塞
}
}
CompletableFuture
可用来管理多个Future的结果,对各种各样的结果进行组合处理。你可以去查查它的API~
提供了很多非常好用的接口,十分友好!
示例:假设你能够提供一个服务,这个服务查询各大电商网站同一类产品的价格并汇总展示,你用CompletableFuture开启三个线程来完成这个任务
import java.io.IOException;
import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
public class T06_01_CompletableFuture {
public static void main(String[] args) throws ExecutionException, InterruptedException {
long start, end;
/*start = System.currentTimeMillis();
priceOfTM();
priceOfTB();
priceOfJD();
end = System.currentTimeMillis();
System.out.println("use serial method call! " + (end - start));*/
start = System.currentTimeMillis();
CompletableFuture<Double> futureTM = CompletableFuture.supplyAsync(() -> priceOfTM());
CompletableFuture<Double> futureTB = CompletableFuture.supplyAsync(() -> priceOfTB());
CompletableFuture<Double> futureJD = CompletableFuture.supplyAsync(() -> priceOfJD());
CompletableFuture.allOf(futureTM, futureTB, futureJD).join(); // 提供对于一堆任务的管理:这三个任务全部完成之后,才能继续向下运行
// CompletableFuture.anyOf(futureTM, futureTB, futureJD).join(); // 任意一个任务完成,就能继续向下运行
CompletableFuture.supplyAsync(() -> priceOfTM())
.thenApply(String::valueOf)
.thenApply(str -> "price " + str)
.thenAccept(System.out::println);
end = System.currentTimeMillis();
System.out.println("use completable future! " + (end - start));
try {
System.in.read();//因为里面全是异步,所以需要阻塞一下,才能正常的等待它们输出,不然主线程先结束了。。
} catch (IOException e) {
e.printStackTrace();
}
}
private static double priceOfTM() {
delay();
return 1.00;
}
private static double priceOfTB() {
delay();
return 2.00;
}
private static double priceOfJD() {
delay();
return 3.00;
}
/*private static double priceOfAmazon() {
delay();
throw new RuntimeException("product not exist!");
}*/
private static void delay() {
int time = new Random().nextInt(500);
try {
TimeUnit.MILLISECONDS.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("After random %s sleep!\n", time);
}
}
jdk中的工具类
对数组操作的工具类:Arrays
对容器操作的工具类:Collections
对线程操作的工具类:Executors
Executors中方法
1 Executors.newSingleThreadExecutor()
特点:LinkedBlockingQueue队列长integer.max,不超时只有一个线程
为什么要有单线程线程池:
1.线程池有任务队列
2.线程池有完整的生命周期管理
2 Executors.newCachedThreadPool()
特点:没有核心线程,60s回收,来一个起一个线程,用完就删除
syncqueue:必须同时进一个出一个,本身不存储元素cap==0,
可能起的线程数会非常多。
3 Executors.newFixedThreadPool(cpuCoreNum);
固定线程数的好处是:
适合做一些并行的计算,比如你要找1-200000之内所有的质数,你将这个大任务拆成4个小线程,共同去运行。
利用线程池进行并行的计算,肯定比串行计算要更快。
并发和并行的区别:
并发指任务提交,并行指任务执行;
并行是多个CPU同时处理,并发是多个任务同时过来;
并行是并发的子集
Catched vs Fixed引出如何调整线程池大小
如何选择(建议都不选,自定义)
加入任务来的时候忽高忽低用catched
如果任务来的平稳就用fixed
按阿里的Java开发手册,这两种都不用,各有缺点,自己估算精确定义
4 Executors.newScheduledThreadPool(4);
定时任务线程池,专门用来执行定时任务,采用DelayedWorkQueue来实现
import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class T10_ScheduledPool {
public static void main(String[] args) {
ScheduledExecutorService service = Executors.newScheduledThreadPool(4);
service.scheduleAtFixedRate(() -> {
try {
TimeUnit.MILLISECONDS.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}, 0, 500, TimeUnit.MILLISECONDS);
}
}
定时任务框架:quarz,cron
5. Executors.newWorkStealingPool();
WorkStealingPool 本质上还是一个 ForkJoinPool
由于一个线程有自己的队列,因此拿自己的队列任务无线程冲突不需要加锁,但stealing其他线程的队列任务需要加锁
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class T11_WorkStealingPool {
public static void main(String[] args) throws IOException {
ExecutorService service = Executors.newWorkStealingPool();
System.out.println(Runtime.getRuntime().availableProcessors());
service.execute(new R(1000));
service.execute(new R(2000));
service.execute(new R(2000));
service.execute(new R(2000)); //daemon
service.execute(new R(2000));
//由于产生的是精灵线程(守护线程、后台线程),主线程不阻塞的话,看不到输出
System.in.read();
}
static class R implements Runnable {
int time;
R(int t) {
this.time = t;
}
@Override
public void run() {
try {
TimeUnit.MILLISECONDS.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(time + " " + Thread.currentThread().getName());
}
}
}
ForkJoinPool
核心:将任务分解,执行后再将结果汇总
RecursiveAction:不带返回值,分解任务可以采用重写compute方法
RecursiveTask:重写compute()带返回值
import java.io.IOException;
import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;
import java.util.concurrent.RecursiveTask;
public class T12_ForkJoinPool {
static int[] nums = new int[1000000];
static final int MAX_NUM = 50000;
static Random r = new Random();
static {
for(int i=0; i<nums.length; i++) {
nums[i] = r.nextInt(100);
}
}
static class AddTaskRet extends RecursiveTask<Long> {
private static final long serialVersionUID = 1L;
int start, end;
AddTaskRet(int s, int e) {
start = s;
end = e;
}
@Override
protected Long compute() {
if(end-start <= MAX_NUM) {
long sum = 0L;
for(int i=start; i<end; i++) sum += nums[i];
return sum;
}
int middle = start + (end-start)/2;
AddTaskRet subTask1 = new AddTaskRet(start, middle);
AddTaskRet subTask2 = new AddTaskRet(middle, end);
subTask1.fork();
subTask2.fork();
return subTask1.join() + subTask2.join();
}
}
public static void main(String[] args) throws IOException {
/*ForkJoinPool fjp = new ForkJoinPool();
AddTask task = new AddTask(0, nums.length);
fjp.execute(task);*/
T12_ForkJoinPool temp = new T12_ForkJoinPool();
ForkJoinPool fjp = new ForkJoinPool();
AddTaskRet task = new AddTaskRet(0, nums.length);
fjp.execute(task);
long result = task.join();
System.out.println(result);
//System.in.read();
}
}
如何定义线程池
阿里《Java开发手册》建议自定义线程池,所以我们先来看看如何自定义一个线程池。
源码解析
维护两个集合:
- 线程集合
- 任务集合
用的是HashSet
常量及常量相关操作
// 1. `ctl`,可以看做一个int类型的数字,高3位表示线程池状态,低29位表示worker数量
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// 2. `COUNT_BITS`,`Integer.SIZE`为32,所以`COUNT_BITS`为29
private static final int COUNT_BITS = Integer.SIZE - 3;
// 3. `CAPACITY`,线程池允许的最大线程数。1左移29位,然后减1,即为 2^29 - 1
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// runState is stored in the high-order bits
// 4. 线程池有5种状态,按大小排序如下:RUNNING < SHUTDOWN < STOP < TIDYING < TERMINATED
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
// Packing and unpacking ctl
// 5. `runStateOf()`,获取线程池状态,通过按位与操作,低29位将全部变成0
private static int runStateOf(int c) { return c & ~CAPACITY; }
// 6. `workerCountOf()`,获取线程池worker数量,通过按位与操作,高3位将全部变成0
private static int workerCountOf(int c) { return c & CAPACITY; }
// 7. `ctlOf()`,根据线程池状态和线程池worker数量,生成ctl值
private static int ctlOf(int rs, int wc) { return rs | wc; }
/*
* Bit field accessors that don't require unpacking ctl.
* These depend on the bit layout and on workerCount being never negative.
*/
// 8. `runStateLessThan()`,线程池状态小于xx
private static boolean runStateLessThan(int c, int s) {
return c < s;
}
// 9. `runStateAtLeast()`,线程池状态大于等于xx
private static boolean runStateAtLeast(int c, int s) {
return c >= s;
}
构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
// 基本类型参数校验
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
// 空指针校验
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
// 根据传入参数`unit`和`keepAliveTime`,将存活时间转换为纳秒存到变量`keepAliveTime `中
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
execute方法
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
int c = ctl.get();
// worker数量比核心线程数小,直接创建worker执行任务
if (workerCountOf(c) < corePoolSize) {
// true 表示核心线程, addWorker会去判断线程池状态
if (addWorker(command, true))
return;
c = ctl.get();
}
// worker数量超过核心线程数,任务直接进入队列
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
// 线程池状态不是RUNNING状态,说明执行过shutdown命令,需要对新加入的任务执行reject()操作。
// 这儿为什么需要recheck,是因为任务入队列前后,线程池的状态可能会发生变化。
if (! isRunning(recheck) && remove(command))
reject(command);
// 这儿为什么需要判断0值,主要是在线程池构造方法中,核心线程数允许为0,此时添加一个非核心线程,由于任务已到队列因此这里不需要传,等worker自己取
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 如果线程池不是运行状态,或者任务进入队列失败,则尝试创建worker执行任务。
// 这儿有3点需要注意:
// 1. 线程池不是运行状态时,addWorker内部会判断线程池状态
// 2. addWorker第2个参数表示是否创建核心线程
// 3. addWorker返回false,则说明任务执行失败,需要执行reject操作
else if (!addWorker(command, false))
reject(command);
}
addWorker
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
// 外层自旋
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// 这个条件写得比较难懂,我对其进行了调整,和下面的条件等价
// (rs > SHUTDOWN) ||
// (rs == SHUTDOWN && firstTask != null) ||
// (rs == SHUTDOWN && workQueue.isEmpty())
// 1. 线程池状态大于SHUTDOWN时,直接返回false(shutdownnow等状态不创建任何worker,队列任务也是抛弃)
// 2. 线程池状态等于SHUTDOWN,且firstTask不为null,直接返回false(表示不接受新任务。firstTask为空有可能是因为核心worker设置为0新建的非核心worker,此时需要执行队列任务因此不能返回false)
// 3. 线程池状态等于SHUTDOWN,且firstTask为null,且队列为空,直接返回false(队列为空不需要建空worker了)
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
// 内层自旋
for (;;) {
int wc = workerCountOf(c);
// worker数量超过容量,直接返回false
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// 使用CAS的方式增加worker数量。(+1)
// 若增加成功,则直接跳出外层循环进入到第二部分
if (compareAndIncrementWorkerCount(c))
break retry;
// 否则,说明CAS失败,ctrl发生了变化已经不是c了,需要重新获取
c = ctl.get(); // Re-read ctl
// 线程池状态发生变化,对外层循环进行自旋
if (runStateOf(c) != rs)
continue retry;
// 其他情况(有别的线程新建worker/扩缩容等),直接内层循环进行自旋即可
// else CAS failed due to workerCount change; retry inner loop
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
// 真正的worker添加
try {
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
// worker的添加必须是串行的,因此需要加锁
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
// 这儿需要重新检查线程池状态
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
// worker已经调用过了start()方法,则不再创建worker
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
// worker创建并添加到workers成功
workers.add(w);
// 更新`largestPoolSize`变量
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
// 启动worker线程
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
// worker线程启动失败,说明线程池状态发生了变化(关闭操作被执行),需要进行shutdown相关操作
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
compareAndIncrementWorkerCount
private boolean compareAndIncrementWorkerCount(int expect) {
return ctl.compareAndSet(expect, expect + 1);
}
addWorkerFailed
Worker类
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
/**
* This class will never be serialized, but we provide a
* serialVersionUID to suppress a javac warning.
*/
private static final long serialVersionUID = 6138294804551838833L;
/** 这个就是worker持有的线程,也就是线程池中的线程 */
final Thread thread;
/** Initial task to run. Possibly null. */
Runnable firstTask;
/** 每一个线程执行的任务数量的计数器 */
volatile long completedTasks;
/**
* Creates with given first task and thread from ThreadFactory.
* @param firstTask the first task (null if none)
*/
Worker(Runnable firstTask) {
// Worker继承AQS,使用AQS实现独占锁,并且是不可重入的,构造Worker对象的时候,会把锁资源状态设置成-1,因为新增的线程,还没有处理过任务,是不允许被中断的
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
// 这儿是Worker的关键所在,使用了线程工厂(入参之一)创建了一个线程。传入的参数为当前worker
this.thread = getThreadFactory().newThread(this);
}
/** Delegates main run loop to outer runWorker */
public void run() {
runWorker(this);
}
// Lock methods
//
// The value 0 represents the unlocked state.
// The value 1 represents the locked state.
protected boolean isHeldExclusively() {
return getState() != 0;
}
protected boolean tryAcquire(int unused) {
// 独占锁,state只有0,1
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
public void lock() { acquire(1); }
public boolean tryLock() { return tryAcquire(1); }
public void unlock() { release(1); }
public boolean isLocked() { return isHeldExclusively(); }
// 如果正在执行任务,则不应该中断线程;
// 如果该线程现在不是独占锁的状态,也就是空闲的状态,说明它没有在处理任务,这时可以对该线程进行中断;
// 线程池在执行 shutdown 方法或 tryTerminate 方法时会调用 interruptIdleWorkers 方法来中断空闲的线程,interruptIdleWorkers 方法会使用 tryLock 方法来判断线程池中的线程是否是空闲状态
// 之所以设置为不可重入,是因为我们不希望任务在调用像 setCorePoolSize 这样的线程池控制方法时重新获取锁,这样会中断正在运行的线程
void interruptIfStarted() {
Thread t;
// 如果state < 0表示初始化,不允许中断
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
}
worker其实就是一个Runable,其也是需要构造成一个Thread对象,然后调用Thread.start()方法运行的。只不过在worker的run方法中是定义了一个runWoker的方法。这个方法的主要内容从 for 循环不停的从task队列中获取对应的runable的task,然后同步调用这个task的run()方法。其实就是在某个线程中,不停的拿队列中的任务进行执行。
为什么给Worker要继承AQS而不是ReentrantLock
为什么不可重入:
如果继承Reentrant,可重入锁会导致主线程的扩缩容和shutdown操作会互相影响
为什么独占
Worker并没有判断lock或unlock的触发线程是不是当前持有锁的线程,因此我们不希望任务在调用像 setCorePoolSize 这样的线程池控制方法时重新获取锁,这样会中断正在运行的线程
小结
一般来说,interruptIdleWorkers()方法的调用不是在本工作线程,而是在主线程中调用的,还记得shutdown()和shutdownNow()方法吗?
观察两个方法中中断线程的方法,shutdown()中就是调用了interruptIdleWorkers()方法来中断线程地,而且interruptIdleWorkers()方法中就用到了tryLock(),只有获取到锁了才能中断线程,如果没有获取到锁则不中断。而调用tryLock()后没获取到锁只有一种原因,那就是lock()所在的地方runWorker()方法中,有任务正在执行。这样shutdown()方法就实现了只中断空闲线程,不会中断正在执行任务的线程。
而shutdownNow()中中断线程则很暴力,并没有tryLock(),而是直接中断了线程,所以调用shutdownNow()可能会中断正在执行的任务。
所以,Worker继承自AQS实际是要使用其锁的能力,这个锁主要是用来控制调用shutdown()时不要中断正在执行任务的线程。
那么为什么Worker使用AQS实现锁,而不直接用ReentrantLock呢?我们可以看到Worker的tryAcquire 方法,它是不允许重入的,而 ReentrantLock是允许重入的。所以这是为了实现不可重入的特性去反应线程现在的执行状态。
interruptIdleWorkers
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
setExclusiveOwnerThread
/**
* Sets the thread that currently owns exclusive access.
* A {@code null} argument indicates that no thread owns access.
* This method does not otherwise impose any synchronization or
* {@code volatile} field accesses.
* @param thread the owner thread
*/
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
runWorker
这里要注意,run()方法是Worker类的方法,但是其调用的runWorker()方法是ThreadPoolExecutor中的方法,run方法若退出(getTask()返回null)说明worker生命走到头了,由JVM销毁
final void runWorker(Worker w) {
// 获取当前线程,其实这个当前线程,就是worker对象持有的线程,从线程池中拿到的任务就是由这个线程执行的
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
// 将AQS锁资源的状态由-1变成0,运行该线程进行中断 因为在创建的时候将state设为-1了,现在开始执行任务了,也就需要加锁了,所以要把state再重新变为0,这样在后面执行任务的时候才能用来加锁,保证任务在执行过程中不会出现并发异常
// 解锁
w.unlock(); // allow interrupts
// 这个变量用于判断是否进入过自旋(while循环)
boolean completedAbruptly = true;
try {
// 这儿是自旋
// 1. 如果firstTask不为null,则执行firstTask;
// 2. 如果firstTask为null,则调用getTask()从队列获取任务。
// 3. 阻塞队列的特性就是:当队列为空时,当前线程会被阻塞等待
while (task != null || (task = getTask()) != null) {
// 这儿对worker进行加锁,是为了达到下面的目的
// 1. 降低锁范围,提升性能
// 2. 保证每个worker执行的任务是串行的
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
// 如果线程池正在停止,则对当前线程进行中断操作
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
// 执行任务,且在执行前后通过`beforeExecute()`和`afterExecute()`来扩展其功能。
// 这两个方法在当前类里面为空实现。
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
// 帮助gc
task = null;
// 已完成任务数加一
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
// 自旋操作被退出,说明线程池正在结束
processWorkerExit(w, completedAbruptly);
}
}
getTask
判断是否设置了超时退出的逻辑都在这里,正常是阻塞在queue的poll或者take或者自旋锁。当返回null时意味着worker设置有超时自毁需要结束了
// 返回任务Runnable
private Runnable getTask() {
// timedOut表示 记录上一次从队列中获取任务是否超时
boolean timedOut = false; // Did the last poll() time out?
// 自旋
for (;;) {
// 这一部分是判断线程池状态
// 获取线程池的状态和线程池中线程数量组成的整形字段,32位
// 高3位代表线程池的状态,低29位代表线程池中线程的数量
int c = ctl.get();
// 获取高3位的值,即线程池的状态
int rs = runStateOf(c);
// 如果线程池状态不是Running状态,并且 线程也不是SHUTDOWN状态 或者任务队列已空
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
// 则将线程池中的线程数量减1 就是说该线程已经不是运行状态了,所以要这个worker线程也没有用了,直接将该worker去掉。这个是原子操作
decrementWorkerCount();
//返回一个空任务,因为:
// 1:如果任务队列已空,则想返回任务也没有
// 2:如果线程池处于STOP或者之上的状态,则线程池不允许再处理任务
return null;
}
// 这一部分是判断线程池有效线程数量
// 获取低29位的值,即线程池中线程的数量
int wc = workerCountOf(c);
// timed是否需要进行超时控制
// allowCoreThreadTimeOut默认false
// 当线程池中线程的数量没有达到核心线程数量时,获取任务的时候允许超时 如果将allowCoreThreadTimeOut设为true,那也不允许超时
// 当线程池中线程的数量超过核心线程数量时,获取任务的时候不允许超时
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; // 这个很好理解
// wc > maximumPoolSize的情况是因为可能在此方法执行阶段同时执行了setMaximumPoolSize方法;
// timed && timedOut 如果为true,表示当前操作需要进行超时控制,并且上次从阻塞队列中获取任务发生了超时
// 接下来判断,如果有效线程数量大于1,或者阻塞队列是空的,那么尝试将workerCount减1;
// 如果减1失败,则continue返回重试
// 如果wc == 1时,也就说明当前线程是线程池中唯一的一个线程了。
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
// 如果上面都没问题,就可以获取任务了
try {
// 获取任务
// 如果timed = true ,说明需要做超时控制,则根据keepAliveTime设置的时间内,阻塞等待从队列中获取任务
// 如果timed = false,说明不需要做超时控制,则阻塞,直到从队列中获取到任务为止
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
// 如果获取到任务,则把任务返回
if (r != null)
return r;
// 执行到这里,说明在允许的时间内,没有获取到任务
timedOut = true;
} catch (InterruptedException retry) {
// 获取任务没有超时,但是出现异常了,将timedOut设置为false
timedOut = false;
}
}
}
poll(timeout, unit)方法会在超时时返回null,如果timeout<=0,队列为空时直接返回null。
take()方法会一直阻塞直到取到任务或抛出中断异常。
allowCoreThreadTimeOut
public void allowCoreThreadTimeOut(boolean value) {
if (value && keepAliveTime <= 0)
throw new IllegalArgumentException("Core threads must have nonzero keep alive times");
if (value != allowCoreThreadTimeOut) {
allowCoreThreadTimeOut = value;
if (value)
// 中断空闲的线程
interruptIdleWorkers();
}
}
processWorkerExit(不重要)
runWorker 的 while 循环执行完毕以后,在 finally 中会调用 processWorkerExit()方法,来销毁工作线程。该方法就是判断当前线程是需要将其删除还是继续执行任务。该方法也是ThreadPoolExecutor中的方法。但这个方法只有在一定情况下才会起作用,如果已经从队列中取不出任务来了,或者在worker执行任务过程中出现了错误,这个方法就起到了作用,如果正常的话这个方法就没啥用。
private void processWorkerExit(ThreadPoolExecutor.Worker w, boolean completedAbruptly) {
// 如果 completedAbruptly = true ,则线程执行任务的时候出现了异常,需要从线程池中减少一个线程
// 如果 completedAbruptly = false,则执行getTask方法的时候已经减1,这里无需在进行减1操作
if (completedAbruptly)
decrementWorkerCount();
// 获取线程池的锁,因为后面是线程池的操作,为了并发安全,需要对线程池加锁
final ReentrantLock mainLock = this.mainLock;
// 线程池加锁
mainLock.lock();
try {
// 统计该线程池完成的任务数
completedTaskCount += w.completedTasks;
// 从线程池中移除一个工作线程 works是线程池持有的一个集合
workers.remove(w); // 将没用的worker去掉,也就是当前传入的worker
} finally {
// 线程池解锁
mainLock.unlock();
}
// 根据线程池的状态,决定是否结束该线程池
tryTerminate(); // 钩子方法
// 判断线程池是否需要增加线程
// 获取线程池的状态
int c = ctl.get();
// -当线程池是RUNNING或SHUTDOWN状态时
// --如果worker是异常结束(即completedAbruptly为false),那么会直接addWorker;
// ---如果allowCoreThreadTimeOut = true,并且等待队列有任务,至少保留一个worker;
// ---如果allowCoreThreadTimeOut = false,活跃线程数不少于corePoolSize
if (runStateLessThan(c, STOP)) { // 线程池状态小于STOP,就说明当前线程池是RUNNING或SHUTDOWN状态
// 如果worker是异常结束的,不进入下面的分支,直接去addWorker
if (!completedAbruptly) {
// 根据allowCoreThreadTimeOut的值,来设置线程池中最少的活跃线程数是0还是corePoolSize
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
// 如果等待队列中有任务,要至少保留一个worker
if (min == 0 && ! workQueue.isEmpty())
// 至少保留一个worker
min = 1;
// 如果活跃线程数大于等于min,直接返回,不需要再调用addWorker来增加线程池中的线程了
if (workerCountOf(c) >= min)
return; // replacement not needed
}
// 增加线程池中的worker
addWorker(null, false);
}
}
shutdown
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
// 上锁,确保同一时间只能有一个线程执行此操作
mainLock.lock();
try {
// 检查方法调用方是否用权限关闭线程池以及中断工作线程
checkShutdownAccess();
// 将线程池运行状态设置为SHUTDOWN
advanceRunState(SHUTDOWN);
// 中断所有空闲线程
interruptIdleWorkers();
// 此方法在ThreadPoolExecutor中是空实现,具体实现在其子类ScheduledThreadPoolExecutor
// 中,用于取消延时任务。
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
// 尝试将线程池置为TERMINATED状态
tryTerminate();
}
advanceRunState
private void advanceRunState(int targetState) {
// 程序循环执行,直到将运行状态设为目标值
for (;;) {
// 获取AtomicInteger变量中的整数值
int c = ctl.get();
// if条件的前半部分是判断当前运行状态是否大于等于给定值
if (runStateAtLeast(c, targetState) ||
// 后半部分利用CAS操作将运行状态设置为目标值,成功的话会返回true,失败则返回false
ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))
break;
}
}
tryTerminate
final void tryTerminate() {
// 依然是CAS自旋操作
for (;;) {
// 获取ctl中的整型值
int c = ctl.get();
// 如果是以下几种情况之一的,对应第一个if判断的几种状况,直接return,不错任何操作
// 1.线程池处于RUNNING状态
// 2.线程池处于TIDYING状态或TERMINATED状态
// 3.线程池处于SHUTDOWN状态但是任务队列不为空
if (isRunning(c) ||
runStateAtLeast(c, TIDYING) ||
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
return;
// 经过上一个if判断的过滤,当前线程池除了线程数不为0,其他条件都已经满足关闭的要求
// 所以进行下一个if判断,如果线程数不为0,则中断一个空闲线程并返回
if (workerCountOf(c) != 0) {
interruptIdleWorkers(ONLY_ONE);
return;
}
// 方法进行到这里说明线程池所有条件都已经满足关闭的要求,下面的操作就是将线程池状态
// 置为TERMINATED
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 将线程池状态置为TIDYING
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
// 本类中是空方法,子类有具体实现,用于进行关闭线程池的前置操作
terminated();
} finally {
// 将线程池置为TERMINATED状态,且线程数为0
ctl.set(ctlOf(TERMINATED, 0));
// 唤醒其他正在等待的线程
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
// else retry on failed CAS,失败的话会继续循环执行CAS操作
}
}
shutdownNow
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 检查方法调用方是否用权限关闭线程池以及中断工作线程
checkShutdownAccess();
// 将线程池运行状态置为STOP
advanceRunState(STOP);
// 中断所有线程,包括正在运行的线程
interruptWorkers();
// 将未执行的任务移入列表中
tasks = drainQueue();
} finally {
mainLock.unlock();
}
// 尝试将线程池置为TERMINATED状态
tryTerminate();
return tasks;
}
interuptWorkers
private void interruptWorkers() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 中断所有线程
for (Worker w : workers)
// 调用内部类Worker自身方法中断线程
w.interruptIfStarted();
} finally {
mainLock.unlock();
}
}
关闭线程池的正确姿势,shutdown(), shutdownNow()和awaitTermination() 该怎么用? - 掘金 (juejin.cn)
Worker工作线程的生命周期
Worker执行任务的模型如下图所示:
面试题
面试:假如提供一个闹钟服务,订阅这个服务的人特别多,10亿人,怎么优化?
1.任务分发到多台边缘服务器
2. 每台服务器用线程池+队列,一个个消费
ThreadPoolExecutor源码解析
线程池的生命周期
线程池有哪些状态?状态是如何转换的?_线程池的状态_CRMEB定制开发的博客-CSDN博客
线程池的状态有以下 5 种:
RUNNING:运行状态,线程池创建好之后就会进入此状态,如果不手动调用关闭方法,那么线程池在整个程序运行期间都是此状态。
SHUTDOWN:关闭状态,不再接受新任务提交,但是会将已保存在任务队列中的任务处理完。
STOP:停止状态,不再接受新任务提交,并且会中断当前正在执行的任务、放弃任务队列中已有的任务。
TIDYING:整理状态,所有的任务都执行完毕后(也包括任务队列中的任务执行完),当前线程池中的活动线程数降为 0 时的状态。到此状态之后,会调用线程池的 terminated() 方法。
TERMINATED:销毁状态,当执行完线程池的 terminated() 方法之后就会变为此状态。
这 5 种状态可以在 ThreadPoolExecutor 源码中找到,如下图所示:
线程池状态转移
线程池的状态转移有两条路径:
当调用 shutdown() 方法时,线程池的状态会从 RUNNING 到 SHUTDOWN,再到 TIDYING,最后到 TERMENATED 销毁状态。
当调用 shutdownNow() 方法时,线程池的状态会从 RUNNING 到 STOP,再到 TIDYING,最后到 TERMENATED 销毁状态。
terminated方法
线程池中的 terminated() 方法,也就是线程池从 TIDYING 转换到 TERMINATED 状态时调用的方法,默认是空的,它的源码如下:
我们可以在创建线程池的时候重写 terminated() 方法
流式API的ForkJoinPool的算法实现
流式API的底层也是使用 ForkJoinPool 来实现的。nums.parallelStream().forEach
这种并行流处理起来效率会更高一些。
package com.mashibing.juc.c_026_01_ThreadPool;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class T13_ParallelStreamAPI {
public static void main(String[] args) {
List<Integer> nums = new ArrayList<>();
Random r = new Random();
for (int i = 0; i < 10000; i++) nums.add(1000000 + r.nextInt(1000000));
//System.out.println(nums);
long start = System.currentTimeMillis();
nums.forEach(v -> isPrime(v));
long end = System.currentTimeMillis();
System.out.println(end - start);
//使用parallel stream api
start = System.currentTimeMillis();
nums.parallelStream().forEach(T13_ParallelStreamAPI::isPrime);
end = System.currentTimeMillis();
System.out.println(end - start);
}
static boolean isPrime(int num) {
for (int i = 2; i <= num / 2; i++) {
if (num % i == 0) return false;
}
return true;
}
}
Disruptor
DisruptorD多线程与高并发(九):单机压测工具JMH,单机最快MQ - Disruptor原理解析_寒泉Hq的博客-CSDN博客