多线程系列4-线程池
java sdk提供的线程池:java.util.concurrent.ThreadPoolExecutor,由java.util.concurrent.Executors 创建,
Spring 提供的线程池 ThreadPoolTaskExecutor
为什么要使用线程池
在开发中,为了提升效率的操作,我们需要将一些业务采用多线程的方式去执行。
比如有一个比较大的任务,可以将任务分成几块,分别交给几个线程去执行,最终做一个汇总就可以了。
比如做业务操作时,需要发送短信或者是发送邮件,这种操作也可以基于异步的方式完成,这种异步的方式,其实就是再构建一个线程去执行。
但是,如果每次异步操作或者多线程操作都需要新创建一个线程,使用完毕后,线程再被销毁,这样的话,对系统造成一些额外的开销。在处理过程中到底由多线程处理了多少个任务,以及每个线程的开销无法统计和管理。
所以咱们需要一个线程池机制来管理这些内容。线程池的概念和连接池类似,都是在一个Java的集合中存储大量的线程对象,每次需要执行异步操作或者多线程操作时,不需要重新创建线程,直接从集合中拿到线程对象直接执行方法就可以了。
JDK中就提供了线程池的类。
在线程池构建初期,可以将任务提交到线程池中。会根据一定的机制来异步执行这个任务。
* 可能任务直接被执行
* 任务可以暂时被存储起来了。等到有空闲线程再来处理。
* 任务也可能被拒绝,无法被执行。
JDK提供的线程池中记录了每个线程处理了多少个任务,以及整个线程池处理了多少个任务。同时还可以针对任务执行前后做一些勾子函数的实现。可以在任务执行前后做一些日志信息,这样可以多记录信息方便后面统计线程池执行任务时的一些内容参数等等……
----------------------------------------------------------
finalize是守护线程,主线程完成了,那么守护线程也就结束了。
局部变量的线程池记得shutdown,不然new出来的线程还执行在系统中
全局的(成员变量)线程池可以不用管。
--------------------------------------------------------------------------------------
只要有任务提交过来,就必然有工作线程来执行,不会等待。在空闲时间内,可能会同一个线程先后执行两个任务。
----------------------------------------------------------------------------------------------------------------------
newScheduleThreadPool
首先看到名字就可以猜到当前线程池是一个定时任务的线程池,而这个线程池就是可以以一定周期去执行一个任务,或者是延迟多久执行一个任务一次
查看一下如何构建的。
所以本质上还是正常线程池,只不过在原来的线程池基础上实现了定时任务的功能
原理是基于DelayQueue实现的延迟执行。周期性执行是任务执行完毕后,再次扔回到阻塞队列。
代码查看使用的方式和效果
至于Executors提供的newSingleThreadScheduledExecutor单例的定时任务线程池就不说了。
一个线程的线程池可以延迟或者以一定的周期执行一个任务。
-------------------------------------------------------------------------------------------------
newWorkStealingPool
主要做的就是一个任务拆分,执行完再聚合。fork and join
代码示例如下:
package myrt; import java.util.concurrent.Executors; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.ForkJoinTask; import java.util.concurrent.RecursiveTask; public class ForkJoinResearch { /** 非常大的数组 */ /** 十亿 */ static int[] nums = new int[1_000_000_000]; // 填充值 static{ for (int i = 0; i < nums.length; i++) { nums[i] = (int) ((Math.random()) * 1000); } } public static void main(String[] args) { // ===================单线程累加10亿数据================================ System.out.println("单线程计算数组总和!"); long start = System.nanoTime(); int sum = 0; for (int num : nums) { sum += num; } long end = System.nanoTime(); System.out.println("单线程运算结果为:" + sum + ",计算时间为:" + (end - start)); // ===================多线程分而治之累加10亿数据================================ // 在使用forkJoinPool时,不推荐使用Runnable和Callable // 可以使用提供的另外两种任务的描述方式 // Runnable(没有返回结果) -> RecursiveAction // Callable(有返回结果) -> RecursiveTask ForkJoinPool forkJoinPool = (ForkJoinPool) Executors.newWorkStealingPool(); System.out.println("分而治之计算数组总和!"); long forkJoinStart = System.nanoTime(); ForkJoinTask<Integer> task = forkJoinPool.submit(new SumRecursiveTask(0, nums.length - 1)); Integer result = task.join(); long forkJoinEnd = System.nanoTime(); System.out.println("分而治之运算结果为:" + result + ",计算时间为:" + (forkJoinEnd - forkJoinStart)); } private static class SumRecursiveTask extends RecursiveTask<Integer> { /** 指定一个线程处理哪个位置的数据 */ private int start,end; private final int MAX_STRIDE = 100_000_000; // 200_000_000: 147964900 // 100_000_000: 145942100 public SumRecursiveTask(int start, int end) { this.start = start; this.end = end; } /** * 需要写计算逻辑和拆分逻辑,放到一起 * 拆分过程是递归,然后再挨个执行 * @return */ @Override protected Integer compute() { // 在这个方法中,需要设置好任务拆分的逻辑以及聚合的逻辑 int sum = 0; int stride = end - start; // 可以看出,第一个if里的就是计算逻辑,跟着的else就是拆分逻辑 if(stride <= MAX_STRIDE){ // 可以处理任务 for (int i = start; i <= end; i++) { sum += nums[i]; } }else{ // 将任务拆分,分而治之。 int middle = (start + end) / 2; // 声明为2个任务 SumRecursiveTask left = new SumRecursiveTask(start, middle); SumRecursiveTask right = new SumRecursiveTask(middle + 1, end); // 分别执行两个任务 left.fork(); right.fork(); // 等待结果,并且获取sum sum = left.join() + right.join(); } return sum; } } }
----------------------------------------------------------------------------------------------------------------------------
自定义线程池
-1的二进制表示:
1111111111111111111111111111111111111111111111111111111111111111
0-1 =-1
1 111
前面一位是符号位
0001111111 ~ 取反过后 11100000000~
// 当前是线程池的核心属性 // 当前的ctl其实就是一个int类型的数值,内部是基于AtomicInteger套了一层,进行运算时,是原子性的。 // ctl表示着线程池中的2个核心状态: // 线程池的状态:ctl的高3位,表示线程池状态 // 工作线程的数量:ctl的低29位,表示工作线程的个数 private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); // Integer.SIZE:在获取Integer的bit位个数 // 声明了一个常量:COUNT_BITS = 29 private static final int COUNT_BITS = Integer.SIZE - 3; 00000000 00000000 00000000 00000001 00100000 00000000 00000000 00000000 00011111 11111111 11111111 11111111 // CAPACITY就是当前工作线程能记录的工作线程的最大个数 private static final int CAPACITY = (1 << COUNT_BITS) - 1; // 线程池状态的表示 // 当前五个状态中,只有RUNNING状态代表线程池没问题,可以正常接收任务处理 // 111:代表RUNNING状态,RUNNING可以处理任务,并且处理阻塞队列中的任务。 private static final int RUNNING = -1 << COUNT_BITS; // 000:代表SHUTDOWN状态,不会接收新任务,正在处理的任务正常进行,阻塞队列的任务也会做完。 private static final int SHUTDOWN = 0 << COUNT_BITS; // 001:代表STOP状态,不会接收新任务,正在处理任务的线程会被中断,阻塞队列的任务一个不管。 private static final int STOP = 1 << COUNT_BITS; // 010:代表TIDYING状态,这个状态是否SHUTDOWN或者STOP转换过来的,代表当前线程池马上关闭,就是过渡状态。 private static final int TIDYING = 2 << COUNT_BITS; // 011:代表TERMINATED状态,这个状态是TIDYING状态转换过来的,转换过来只需要执行一个terminated方法。 private static final int TERMINATED = 3 << COUNT_BITS; // 在使用下面这几个方法时,需要传递ctl进来 // 基于&运算的特点,保证只会拿到ctl高三位的值。 private static int runStateOf(int c) { return c & ~CAPACITY; } // 基于&运算的特点,保证只会拿到ctl低29位的值。 private static int workerCountOf(int c) { return c & CAPACITY; }
线程池核心属性:
核心属性:CTL
高3位表示线程的状态,低29位表示工作线程的个数
这些成员变量后面都会赋值给局部变量来使用。
线程池里的线程是懒加载的
// 提交任务到线程池的核心方法 // command就是提交过来的任务 public void execute(Runnable command) { // 提交的任务不能为null if (command == null) throw new NullPointerException(); // 获取核心属性ctl,用于后面的判断 int c = ctl.get(); // 如果工作线程个数,小于核心线程数。 // 满足要求,添加核心工作线程 if (workerCountOf(c) < corePoolSize) { // addWorker(任务,是核心线程吗) // addWorker返回true:代表添加工作线程成功 // addWorker返回false:代表添加工作线程失败 // addWorker中会基于线程池状态,以及工作线程个数做判断,查看能否添加工作线程 if (addWorker(command, true)) // 工作线程构建出来了,任务也交给command去处理了。 return; // 说明线程池状态或者是工作线程个数发生了变化,导致添加失败,重新获取一次ctl c = ctl.get(); } // 添加核心工作线程失败,往这走 // 判断线程池状态是否是RUNNING,如果是,正常基于阻塞队列的offer方法,将任务添加到阻塞队列 if (isRunning(c) && workQueue.offer(command)) { // 如果任务添加到阻塞队列成功,走if内部 // 如果任务在扔到阻塞队列之前,线程池状态突然改变了。 // 重新获取ctl int recheck = ctl.get(); // 如果线程池的状态不是RUNNING,将任务从阻塞队列移除, if (!isRunning(recheck) && remove(command)) // 并且直接拒绝策略 reject(command); // 在这,说明阻塞队列有我刚刚放进去的任务 // 查看一下工作线程数是不是0个 // 如果工作线程为0个,需要添加一个非核心工作线程去处理阻塞队列中的任务 // 发生这种情况有两种: // 1. 构建线程池时,核心线程数是0个。 // 2. 即便有核心线程,可以设置核心线程也允许超时,设置allowCoreThreadTimeOut为true,代表核心线程也可以超时 else if (workerCountOf(recheck) == 0) // 为了避免阻塞队列中的任务饥饿,添加一个非核心工作线程去处理 addWorker(null, false); } // 任务添加到阻塞队列失败 // 构建一个非核心工作线程 // 如果添加非核心工作线程成功,直接完事,告辞 else if (!addWorker(command, false)) // 添加失败,执行决绝策略 reject(command); }
// 添加工作线程之校验源码 private boolean addWorker(Runnable firstTask, boolean core) { // 外层for循环在校验线程池的状态 // 内层for循环是在校验工作线程的个数 // retry是给外层for循环添加一个标记,是为了方便在内层for循坏跳出外层for循环 retry: for (;;) { // 获取ctl int c = ctl.get(); // 拿到ctl的高3位的值 int rs = runStateOf(c); //==========================线程池状态判断================================================== // 如果线程池状态是SHUTDOWN,并且此时阻塞队列有任务,工作线程个数为0,添加一个工作线程去处理阻塞队列的任务 // 判断线程池的状态是否大于等于SHUTDOWN,如果满足,说明线程池不是RUNNING if (rs >= SHUTDOWN && // 如果这三个条件都满足,就代表是要添加非核心工作线程去处理阻塞队列任务 // 如果三个条件有一个没满足,返回false,配合!,就代表不需要添加 !(rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty())) // 不需要添加工作线程 return false; for (;;) { //==========================工作线程个数判断================================================== // 基于ctl拿到低29位的值,代表当前工作线程个数 int wc = workerCountOf(c); // 如果工作线程个数大于最大值了,不可以添加了,返回false if (wc >= CAPACITY || // 基于core来判断添加的是否是核心工作线程 // 如果是核心:基于corePoolSize去判断 // 如果是非核心:基于maximumPoolSize去判断 wc >= (core ? corePoolSize : maximumPoolSize)) // 代表不能添加,工作线程个数不满足要求 return false; // 针对ctl进行 + 1,采用CAS的方式 if (compareAndIncrementWorkerCount(c)) // CAS成功后,直接退出外层循环,代表可以执行添加工作线程操作了。 break retry; // 重新获取一次ctl的值 c = ctl.get(); // 判断重新获取到的ctl中,表示的线程池状态跟之前的是否有区别 // 如果状态不一样,说明有变化,重新的去判断线程池状态 if (runStateOf(c) != rs) // 跳出一次外层for循环 continue retry; } } // 省略添加工作线程以及启动的过程 }
private boolean addWorker(Runnable firstTask, boolean core) { // 省略校验部分的代码 // 添加工作线程以及启动工作线程~~~ // 声明了三个变量 // 工作线程启动了没,默认false boolean workerStarted = false; // 工作线程添加了没,默认false boolean workerAdded = false; // 工作线程,默认为null Worker w = null; try { // 构建工作线程,并且将任务传递进去 w = new Worker(firstTask); // 获取了Worker中的Thread对象 final Thread t = w.thread; // 判断Thread是否不为null,在new Worker时,内部会通过给予的ThreadFactory去构建Thread交给Worker // 一般如果为null,代表ThreadFactory有问题。 if (t != null) { // 加锁,保证使用workers成员变量以及对largestPoolSize赋值时,保证线程安全 final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { // 再次获取线程池状态。 int rs = runStateOf(ctl.get()); // 再次判断 // 如果满足 rs < SHUTDOWN 说明线程池是RUNNING,状态正常,执行if代码块 // 如果线程池状态为SHUTDOWN,并且firstTask为null,添加非核心工作处理阻塞队列任务 if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) { // 到这,可以添加工作线程。 // 校验ThreadFactory构建线程后,不能自己启动线程,如果启动了,抛出异常 if (t.isAlive()) throw new IllegalThreadStateException(); // private final HashSet<Worker> workers = new HashSet<Worker>(); // 将new好的Worker添加到HashSet中。 workers.add(w); // 获取了HashSet的size,拿到工作线程个数 int s = workers.size(); // largestPoolSize在记录最大线程个数的记录 // 如果当前工作线程个数,大于最大线程个数的记录,就赋值 if (s > largestPoolSize) largestPoolSize = s; // 添加工作线程成功 workerAdded = true; } } finally { mainLock.unlock(); } // 如果工作线程添加成功, if (workerAdded) { // 直接启动Worker中的线程 t.start(); // 启动工作线程成功 workerStarted = true; } } } finally { // 做补偿的操作,如果工作线程启动失败,将这个添加失败的工作线程处理掉 if (!workerStarted) addWorkerFailed(w); } // 返回工作线程是否启动成功 return workerStarted; } // 工作线程启动失败,需要不的步长操作 private void addWorkerFailed(Worker w) { // 因为操作了workers,需要加锁 final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { // 如果w不为null,之前Worker已经new出来了。 if (w != null) // 从HashSet中移除 workers.remove(w); // 同时对ctl进行 - 1,代表去掉了一个工作线程个数 decrementWorkerCount(); // 因为工作线程启动失败,判断一下状态的问题,是不是可以走TIDYING状态最终到TERMINATED状态了。 tryTerminate(); } finally { // 释放锁 mainLock.unlock(); } }
// Worker继承了AQS,目的就是为了控制工作线程的中断。 // Worker实现了Runnable,内部的Thread对象,在执行start时,必然要执行Worker中断额一些操作 private final class Worker extends AbstractQueuedSynchronizer implements Runnable{ // =======================Worker管理任务================================ // 线程工厂构建的线程 final Thread thread; // 当前Worker要执行的任务 Runnable firstTask; // 记录当前工作线程处理了多少个任务。 volatile long completedTasks; // 有参构造 Worker(Runnable firstTask) { // 将State设置为-1,代表当前不允许中断线程 setState(-1); // 任务赋值 this.firstTask = firstTask; // 基于线程工作构建Thread,并且传入的Runnable是Worker this.thread = getThreadFactory().newThread(this); } // 当thread执行start方法时,调用的是Worker的run方法, public void run() { // 任务执行时,执行的是runWorker方法 runWorker(this); } // =======================Worker管理中断================================ // 当前方法是中断工作线程时,执行的方法 void interruptIfStarted() { Thread t; // 只有Worker中的state >= 0的时候,可以中断工作线程 if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) { try { // 如果状态正常,并且线程未中断,这边就中断线程 t.interrupt(); } catch (SecurityException ignore) { } } } protected boolean isHeldExclusively() { return getState() != 0; } protected boolean tryAcquire(int unused) { 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(); } }
工作的线程创建好过后是放到了一个hashSet中。
// 工作线程启动后执行的任务。 final void runWorker(Worker w) { // 拿到当前线程 Thread wt = Thread.currentThread(); // 从worker对象中拿到任务 Runnable task = w.firstTask; // 将Worker中的firstTask置位空 w.firstTask = null; // 将Worker中的state置位0,代表当前线程可以中断的 w.unlock(); // allow interrupts // 判断工作线程是否是异常结束,默认就是异常结束 boolean completedAbruptly = true; try { // 获取任务 // 直接拿到第一个任务去执行 // 如果第一个任务为null,去阻塞队列中获取任务 while (task != null || (task = getTask()) != null) { // 执行了Worker的lock方法,当前在lock时,shutdown操作不能中断当前线程,因为当前线程正在处理任务 w.lock(); // 比较ctl >= STOP,如果满足找个状态,说明线程池已经到了STOP状态甚至已经要凉凉了 // 线程池到STOP状态,并且当前线程还没有中断,确保线程是中断的,进到if内部执行中断方法 // if(runStateAtLeast(ctl.get(), STOP) && !wt.isInterrupted()) {中断线程} // 如果线程池状态不是STOP,确保线程不是中断的。 // 如果发现线程中断标记位是true了,再次查看线程池状态是大于STOP了,再次中断线程 // 这里其实就是做了一个事情,如果线程池状态 >= STOP,确保线程中断了。 if ( ( runStateAtLeast(ctl.get(), STOP) || ( Thread.interrupted() && runStateAtLeast(ctl.get(), STOP) ) ) && !wt.isInterrupted()) wt.interrupt(); 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 { // 任务执行完,丢掉任务 task = null; // 当前工作线程处理的任务数+1 w.completedTasks++; // 执行unlock方法,此时shutdown方法才可以中断当前线程 w.unlock(); } } // 如果while循环结束,正常走到这,说明是正常结束 // 正常结束的话,在getTask中就会做一个额外的处理,将ctl - 1,代表工作线程没一个。 completedAbruptly = false; } finally { // 考虑干掉工作线程 processWorkerExit(w, completedAbruptly); } } // 工作线程结束前,要执行当前方法 private void processWorkerExit(Worker w, boolean completedAbruptly) { // 如果是异常结束 if (completedAbruptly) // 将ctl - 1,扣掉一个工作线程 decrementWorkerCount(); // 操作Worker,为了线程安全,加锁 final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { // 当前工作线程处理的任务个数累加到线程池处理任务的个数属性中 completedTaskCount += w.completedTasks; // 将工作线程从hashSet中移除 workers.remove(w); } finally { // 释放锁 mainLock.unlock(); } // 只要工作线程凉了,查看是不是线程池状态改变了。 tryTerminate(); // 获取ctl int c = ctl.get(); // 判断线程池状态,当前线程池要么是RUNNING,要么是SHUTDOWN if (runStateLessThan(c, STOP)) { // 如果正常结束工作线程 if (!completedAbruptly) { // 如果核心线程允许超时,min = 0,否则就是核心线程个数 int min = allowCoreThreadTimeOut ? 0 : corePoolSize; // 如果min == 0,可能会出现没有工作线程,并且阻塞队列有任务没有线程处理 if (min == 0 && ! workQueue.isEmpty()) // 至少要有一个工作线程处理阻塞队列任务 min = 1; // 如果工作线程个数 大于等于1,不怕没线程处理,正常return if (workerCountOf(c) >= min) return; } // 异常结束,为了避免出现问题,添加一个空任务的非核心线程来填补上刚刚异常结束的工作线程 addWorker(null, false); } }
线程执行完任务,只需要将runable 设置为null,task = null;
处理非正常结束的线程,如果工作线程个数为0,那么需要唤醒阻塞队列的线程。
核心逻辑:
小于核心线程数 -> 直接执行,return;
添加等待队列 -> 添加成功,等待唤醒
添加不成功,则尝试添加 工作队列,添加工作队列有两种方式,一种是核心线程方式添加,另外一种是非核心线程添加。
// 当前方法就在阻塞队列中获取任务 // 前面半部分是判断当前工作线程是否可以返回null,结束。 // 后半部分就是从阻塞队列中拿任务 private Runnable getTask() { // timeOut默认值是false。 boolean timedOut = false; // 死循环 for (;;) { // 拿到ctl int c = ctl.get(); // 拿到线程池的状态 int rs = runStateOf(c); // 如果线程池状态是STOP,没有必要处理阻塞队列任务,直接返回null // 如果线程池状态是SHUTDOWN,并且阻塞队列是空的,直接返回null if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) { // 如果可以返回null,先扣减工作线程个数 decrementWorkerCount(); // 返回null,结束runWorker的while循环 return null; } // 基于ctl拿到工作线程个数 int wc = workerCountOf(c); // 核心线程允许超时,timed为true // 工作线程个数大于核心线程数,timed为true boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; if ( // 如果工作线程个数,大于最大线程数。(一般情况不会满足),把他看成false // 第二个判断代表,只要工作线程数小于等于核心线程数,必然为false // 即便工作线程个数大于核心线程数了,此时第一次循环也不会为true,因为timedOut默认值是false // 考虑第二次循环了,因为循环内部必然有修改timeOut的位置 (wc > maximumPoolSize || (timed && timedOut)) && // 要么工作线程还有,要么阻塞队列为空,并且满足上述条件后,工作线程才会走到if内部,结束工作线程 (wc > 1 || workQueue.isEmpty()) ) { // 第二次循环才有可能到这。 // 正常结束,工作线程 - 1,因为是CAS操作,如果失败了,重新走for循环 if (compareAndDecrementWorkerCount(c)) return null; continue; } // 工作线程从阻塞队列拿任务 try { // 如果是核心线程,timed是false,如果是非核心线程,timed就是true Runnable r = timed ? // 如果是非核心,走poll方法,拿任务,等待一会 workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : // 如果是核心,走take方法,死等。 workQueue.take(); // 从阻塞队列拿到的任务不为null,这边就正常返回任务,去执行 if (r != null) return r; // 说明当前线程没拿到任务,将timeOut设置为true,在上面就可以返回null退出了。 timedOut = true; } catch (InterruptedException retry) { timedOut = false; } } }
线程池源代码里为什么有这么多的状态判断,因为加锁过后,状态才固定了。这种就是加锁后判断。
runWorker(方法里),是要一直去获取执行任务的,假如如果getTask()获取不到任务了,那么就是没任务了,核心线程假如也到超时时间了,那么就会返回null,退出while循环,执行
processWorkerExit(),线程销毁逻辑。销毁逻辑就是HashSet<worker>.remove()。
线程池调用了shutdown方法,就会将线程池状态从RUNNING
改为SHUTDOWN
,此时不再接受新任务(execute()
方法会拒绝新任务。再者会将空闲中的线程给设置中断状态。在getTask()
中检测到SHUTDOWN
状态且队列为空时返回null
,触发线程退出。
问题:
1、如何中断
定时任务线程池、定时任务线程池、定时任务线程池、定时任务线程池