多线程一直Java开发中的难点,也是面试中的常客,趁着还有时间,打算巩固一下JUC方面知识,我想机会随处可见,但始终都是留给有准备的人的,希望我们都能加油!!!
沉下去,再浮上来
,我想我们会变的不一样的。
🚤Fork&Join框架
1)介绍
Fork/Join框架是从Java1.7开始提供的一个并行处理任务的框架,它的基本思路是将一个大任务分解成若干个小任务,并行处理多个小任务,最后再汇总合并这些小任务的结果便可得到原来的大任务结果。
通俗点说它就是一个事情划分给好几个人做,效率得到显著提升。
1、Fork :递归式的将大任务分割成合适大小的小任务。
2、Join:执行任务并合并结果。
2)相关类
我们要使用 Fork/Join 框架,首先需要创建一个 ForkJoin 任务。 ForkJoin 类提供了在任务中执行 fork 和 join 的机制。
通常情况下我们都是直接继承ForkJoinTask 的子类,Fork/Join框架提供了两个子类:
- RecursiveAction:一个递归无结果的ForkJoinTask(没有返回值)任务
- RecursiveTask:一个递归有结果的ForkJoinTask(有返回值)任务
- ForkJoinTask 主要方法:
fork() // 在当前线程运行的线程池中安排一个异步执行。简单的理解就是再创建一个子任务。 join() //当任务完成的时候返回计算结果。 invoke() //开始执行任务,如果必要,等待计算完成。
ForkJoinPool:另外ForkJoinTask需要通过 ForkJoinPool 来执行
RecursiveTask:一个递归有结果的ForkJoinTask(有返回值)任务
🚁Fork 方法
调用fork方法时,程序会将新创建的子任务放入当前线程的workQueue队列中,Fork/Join框架将根据当前正在并发执行的ForkJoinTask任务的ForkJoinWorkerThread线程状态,来决定是让这个任务在队列中等待,还是创建一个新的ForkJoinWorkerThread线程运行它,又或者是唤起其它正在等待任务的ForkJoinWorkerThread线程运行它。
public final ForkJoinTask<V> fork() { Thread t; if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ((ForkJoinWorkerThread)t).workQueue.push(this); else //将给定的任务添加到提交者当前队列的提交队列中,如果为 null 或存在竞争,则创建一个。 ForkJoinPool.common.externalPush(this); return this; }
push方法把当前任务存放在 ForkJoinTask 数组队列里。然后再调用 ForkJoinPool 的 signalWork()方法唤醒或创建一个工作线程来执行任务。代码如下:
//推送任务。 仅由非共享队列中的所有者调用。 final void push(ForkJoinTask<?> task) { ForkJoinTask<?>[] a; int s = top, d, cap, m; ForkJoinPool p = pool; if ((a = array) != null && (cap = a.length) > 0) { QA.setRelease(a, (m = cap - 1) & s, task); top = s + 1; if (((d = s - (int)BASE.getAcquire(this)) & ~1) == 0 && p != null) { // size 0 or 1 VarHandle.fullFence(); // signalWork方法的意义在于,如果运行的工作程序太少,则尝试创建或释放工作程序。 p.signalWork(); } // 如果array的剩余空间不够了,则进行增加 else if (d == m) growArray(false); } }
🪂Join 方法
Join 方法的主要作用是阻塞当前线程并等待获取结果。代码如下:
通过 doJoin()方法得到当前任务的状态来判断返回什么结果,任务状态有 4 种:已完成(NORMAL)、被取消(CANCELLED)、信号(SIGNAL)和出现异常(EXCEPTIONAL)
public final V join() { int s; if ((s = doJoin() & DONE_MASK) != NORMAL) //假如任务状态是抛出异常状态,就直接抛出对应的异常 //若是任务状态是被取消状态,则直接抛出CancellationException异常。 reportException(s); //如若任务状态是已经完成,则直接立马返回任务结果。 //即使此任务异常完成,如果不知道此任务已完成,则返回null return getRawResult(); }
让我们分析一下 doJoin 方法的实现
private int doJoin() { int s; Thread t; ForkJoinWorkerThread wt; ForkJoinPool.WorkQueue w; //1. 首先通过查看任务的状态,看任务是否已经执行完成,如果执行完成,则直接返回任务状态; //2. 如果没有执行完任务,则从任务数组里取出任务并执行。 //3. 如果任务顺利执行完成,则设置任务状态为 NORMAL,假如出现异常,则记录异常,并将任务状态设置为 EXCEPTIONAL。 return (s = status) < 0 ? s : ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ? (w = (wt = (ForkJoinWorkerThread)t).workQueue). tryUnpush(this) && (s = doExec()) < 0 ? s : //帮助和/或阻塞,直到给定的任务完成或超时 wt.pool.awaitJoin(w, this, 0L) : externalAwaitDone(); }
🚤Fork/Join 框架的异常处理
ForkJoinTask 在执行的时候可能会抛出异常,但因为它并不是在主线程中运行,故此没有办法在主线程中去捕获异常,这种问题既然是历史遗留, ForkJoinTas这个后来者当然也是提供了API来处理的啦,如下:
//如果此任务引发异常或被取消,则返回true 。 通常用来判断任务情况 public final boolean isCompletedAbnormally() { return (status & ABNORMAL) != 0; }
//getException 方法返回 Throwable 对象,如果任务被取消了则返回CancellationException,如果任务没有完成或者没有抛出异常则返回 null。 public final Throwable getException() { int s = status; return ((s & ABNORMAL) == 0 ? null : (s & THROWN) == 0 ? new CancellationException() : getThrowableException()); }
🌍入门案例
场景: 生成一个计算任务,计算 1+2+3.........+1000,每 100 个数切分一个 子任务
import java.util.concurrent.RecursiveTask; /** * 递归累加 */ public class TaskExample extends RecursiveTask<Long> { private int start; private int end; private long sum; /** * 构造函数 * @param start * @param end */ public TaskExample(int start, int end){ this.start = start; this.end = end; } @Override protected Long compute() { System.out.println("任务" + start + "=========" + end + "累加开始"); //大于 100 个数相加切分,小于直接加 if(end - start <= 100){ for (int i = start; i <= end; i++) { //累加 sum += i; } }else { //切分为 2 块 int middle = start + 100; //递归调用,切分为 2 个小任务 TaskExample taskExample1 = new TaskExample(start, middle); TaskExample taskExample2 = new TaskExample(middle + 1, end); //执行:异步 taskExample1.fork(); taskExample2.fork(); //同步阻塞获取执行结果 sum = taskExample1.join() + taskExample2.join(); } //加完返回 return sum; } }
import java.util.concurrent.ForkJoinPool; import java.util.concurrent.ForkJoinTask; /** * 分支合并案例 */ public class ForkJoinPoolDemo { /** * 生成一个计算任务,计算 1+2+3.........+1000 * @param args */ public static void main(String[] args) { //定义任务 TaskExample taskExample = new TaskExample(1, 1000); //定义执行对象 ForkJoinPool forkJoinPool = new ForkJoinPool(); //加入任务执行 ForkJoinTask<Long> result = forkJoinPool.submit(taskExample); //输出结果 try { System.out.println(result.get()); }catch (Exception e){ e.printStackTrace(); }finally { forkJoinPool.shutdown(); } } }
🌈自言自语
最近又开始了JUC的学习,感觉Java内容真的很多,但是为了能够走的更远,还是觉得应该需要打牢一下基础。
最近在持续更新中,如果你觉得对你有所帮助,也感兴趣的话,关注我吧,让我们
一起学习,一起讨论吧。
你好,我是博主宁在春
,Java学习路上的一颗小小的种子,也希望有一天能扎根长成苍天大树。
希望与君共勉
😁
我们:待别时相见时,都已有所成。