面试官: Fork/Join 有了解过吗?说说看(含源码分析)

简介: 面试官: Fork/Join 有了解过吗?说说看(含源码分析)

前言

目前正在出一个Java多线程专题长期系列教程,从入门到进阶含源码解读, 篇幅会较多, 喜欢的话,给个关注❤️ ~


本节给大家介绍一个同样实现了ExecutorService接口的多线程处理器Fork/Join框架,一起来看下吧~


Fork/Join

首先从字面意思讲,fork有分发的意思,join有聚合的意思,所以大胆猜测一下,fork就是把一个大的任务拆分成诸多小的任务,join就是将最终的结果结合起来。


下面我们通过一个累加的demo快速的认识一下它,这里尽量的简单,让大家好理解 ~

public class ForkJoinTest {
    // 首先继承 RecursiveTask 并重写compute方法
    public static class TaskDemo extends RecursiveTask<Integer> {
        private Integer i = 0;
        private Integer num;
        public TaskDemo(Integer num) {
            this.num = num;
        }
        @Override
        protected Integer compute() {
            // 判断任务是否拆分到合适数量
            if(num > 20) {
                // 任务拆分完成后进行计算
                for(int index=0; index < 200; index++) {
                    i ++;
                }
                return i;
            }else {
                // 拆分成两个任务
                TaskDemo t1 = new TaskDemo(num + 1);
                t1.fork();
                TaskDemo t2 = new TaskDemo(num + 2);
                t2.fork();
                return t1.join() + t2.join();
            }
        }
    }
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Long start = System.currentTimeMillis();
        // 创建线程池
        ForkJoinPool pool = new ForkJoinPool();
        // 捕获返回的最终结果
        ForkJoinTask<Integer> taskFuture = pool.submit(new TaskDemo(0));
        Integer result = taskFuture.get();
        System.out.println("最终结果 >>>> " + result);
        Long end = System.currentTimeMillis();
        System.out.println("耗时 >>> " + (end -start) + "ms");
    }
}
复制代码


实际输出

最终结果 >>>> 5731400
耗时 >>> 170ms
复制代码


可以看到,执行速度非常的快,它可以发挥cpu最大性能,任务越大越明显。从上面的用法来看,Fork/Join框架简单来说就是切割任务与子任务进行合并,最终返回结果。


知其然知其所以然 & 源码分析

下面就带大家简单的看一下它的源码实现,我们重点看以下几个方法:


fork()

方法: 使用线程池中的空闲线程异步提交任务

public final ForkJoinTask<V> fork() {
    Thread t;
    // ForkJoinWorkerThread是执行ForkJoinTask的专有线程,由ForkJoinPool管理
    // 先判断当前线程是否是ForkJoin专有线程,如果是,则将任务push到当前线程所负责的队列里去
    if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
        ((ForkJoinWorkerThread)t).workQueue.push(this);
    else
      // 如果不是则将线程加入队列
        // 没有显式创建ForkJoinPool的时候走这里,提交任务到默认的common线程池中,之前的例子是手动创建了一个
        ForkJoinPool.common.externalPush(this);
    return this;
}
复制代码


这个方法只做一件事情, 把任务推入当前工作线程的工作队列里


join()

方法: 等待处理任务的线程处理完毕,获得返回值。

public final V join() {
    int s;
    // doJoin()来获取当前任务的执行状态
    if ((s = doJoin() & DONE_MASK) != NORMAL)
        reportException(s);
    // 最后返回结果
    return getRawResult();
}
复制代码


doJoin() 方法用来返回当前任务的执行状态

private int doJoin() {
    int s; Thread t; ForkJoinWorkerThread wt; ForkJoinPool.WorkQueue w;
    // 先判断任务是否执行完毕,执行完毕直接返回结果(执行状态)
    return (s = status) < 0 ? s :
    // 如果没有执行完毕,先判断是否是ForkJoinWorkThread线程
        ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ?
        // 如果是,先判断任务是否处于工作队列顶端(意味着下一个就执行它)
        // tryUnpush()方法判断任务是否处于当前工作队列顶端,是返回true
        // doExec()方法执行任务
        (w = (wt = (ForkJoinWorkerThread)t).workQueue).
        // 如果是处于顶端并且任务执行完毕,返回结果
        tryUnpush(this) && (s = doExec()) < 0 ? s :
        // 如果不在顶端或者在顶端却没执行完毕,那就调用awitJoin()执行任务
        // awaitJoin():使用自旋使任务执行完成,返回结果
        wt.pool.awaitJoin(w, this, 0L) :
        // 如果不是ForkJoinWorkThread线程,执行externalAwaitDone()返回任务结果
        externalAwaitDone();
}
复制代码


工作窃取算法

Fork/Join框架在处理任务时使用了工作窃取算法, 工作窃取算法指的是在多线程执行不同任务队列的过程中,某个线程执行完自己队列的任务后从其他线程的任务队列里窃取任务来执行。


当一个线程窃取另一个线程的时候,为了减少两个任务线程之间的竞争,我们通常使用双端队列来存储任务。被窃取的任务线程都从双端队列的头部进行任务执行,而窃取其他任务的线程从双端队列的尾部执行任务。


另外,当一个线程在窃取任务时没有可用任务,就会进入阻塞状态以等待再次工作


结束语

本节主要讲解它的一个使用,有兴趣的同学可以继续看一下它的底层源码实现。下一节,给大家讲下Stream流式计算多线程处理,关注我,不迷路 ~

相关文章
|
存储 安全 Java
一天十道Java面试题----第二天(HashMap和hashTable的区别--------》sleep、wait、join)
这篇文章是关于Java面试的第二天笔记,涵盖了HashMap与HashTable的区别、ConcurrentHashMap的实现原理、IOC容器的实现方法、字节码的概念和作用、Java类加载器的类型、双亲委派模型、Java异常体系、GC如何判断对象可回收、线程的生命周期及状态,以及sleep、wait、join、yield的区别等十道面试题。
一天十道Java面试题----第二天(HashMap和hashTable的区别--------》sleep、wait、join)
|
存储 并行计算 安全
Java面试题:请解释Java并发工具包中的主要组件及其应用场景,请描述一个使用Java并发框架(如Fork/Join框架)解决实际问题的编程实操问题
Java面试题:请解释Java并发工具包中的主要组件及其应用场景,请描述一个使用Java并发框架(如Fork/Join框架)解决实际问题的编程实操问题
105 0
|
并行计算 算法 Java
Java面试题:解释Java中的无锁编程的概念,Java中的Fork/Join框架的作用和使用场景,Java中的CompletableFuture的作用和使用场景
Java面试题:解释Java中的无锁编程的概念,Java中的Fork/Join框架的作用和使用场景,Java中的CompletableFuture的作用和使用场景
101 0
|
存储 SQL 缓存
MySQL面试常见之数据表分区设计& 查询缓存&字符集修改&join&varchar
MySQL面试常见之数据表分区设计& 查询缓存&字符集修改&join&varchar
329 0
MySQL面试常见之数据表分区设计& 查询缓存&字符集修改&join&varchar
|
机器学习/深度学习 分布式计算 并行计算
面试官:说说你对Fork/Join的并行计算框架的了解?
面试官:说说你对Fork/Join的并行计算框架的了解?
面试官:说说你对Fork/Join的并行计算框架的了解?
|
SQL
面试之SQL(2)--left join, inner join 和 right join的区别
表A记录如下: aID        aName 1           a1 2           a2 3           a3 4           a4 5           a5 表B记录如下: bID  ...
1147 0
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
11月前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
11月前
|
存储 缓存 算法
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
本文介绍了多线程环境下的几个关键概念,包括时间片、超线程、上下文切换及其影响因素,以及线程调度的两种方式——抢占式调度和协同式调度。文章还讨论了减少上下文切换次数以提高多线程程序效率的方法,如无锁并发编程、使用CAS算法等,并提出了合理的线程数量配置策略,以平衡CPU利用率和线程切换开销。
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
|
11月前
|
存储 缓存 Java
大厂面试必看!Java基本数据类型和包装类的那些坑
本文介绍了Java中的基本数据类型和包装类,包括整数类型、浮点数类型、字符类型和布尔类型。详细讲解了每种类型的特性和应用场景,并探讨了包装类的引入原因、装箱与拆箱机制以及缓存机制。最后总结了面试中常见的相关考点,帮助读者更好地理解和应对面试中的问题。
276 4