《java.util.concurrent 包源码阅读》22 Fork/Join框架的初体验
JDK7引入了Fork/Join框架,所谓Fork/Join框架,个人解释:Fork分解任务成独立的子任务,用多线程去执行这些子任务,Join合并子任务的结果。这样就能使用多线程的方式来执行一个任务。
JDK7引入的Fork/Join有三个核心类:
ForkJoinPool,执行任务的线程池
ForkJoinWorkerThread,执行任务的工作线程
ForkJoinTask,一个用于ForkJoinPool的任务抽象类。
因为ForkJoinTask比较复杂,抽象方法比较多,日常使用时一般不会继承ForkJoinTask来实现自定义的任务,而是继承ForkJoinTask的两个子类:
RecursiveTask:子任务带返回结果时使用
RecursiveAction:子任务不带返回结果时使用
对于Fork/Join框架的原理,Doug Lea的文章:A Java Fork/Join Framework
在看了网上的很多例子之后,发现在自定义任务类实现compute方法的逻辑一般是这样的:
if 任务足够小
直接返回结果
else
分割成N个子任务
依次调用每个子任务的fork方法执行子任务
依次调用每个子任务的join方法合并执行结果
而执行该自定义任务的调用的则是ForkJoinPool的execute方法,因此首先来看的就是ForkJoinPool的execute方法,看看和普通线程池执行任务有什么不同:
    public void execute(ForkJoinTask<?> task) {
        if (task == null)
            throw new NullPointerException();
        forkOrSubmit(task);
    }
因此forkOrSubmit是真正执行ForkJoinTask的方法:
    private <T> void forkOrSubmit(ForkJoinTask<T> task) {
        ForkJoinWorkerThread w;
        Thread t = Thread.currentThread();
        if (shutdown)
            throw new RejectedExecutionException();
        if ((t instanceof ForkJoinWorkerThread) &&
            (w = (ForkJoinWorkerThread)t).pool == this)
            w.pushTask(task);
        else
            // 正常执行的时候是主线程调用的,因此关注addSubmission
            addSubmission(task);
    }
那么我们首先要关注的是addSubmission方法,发觉所做的事情和普通线程池很类似,就是把任务加入到队列中,不同的是直接使用Unsafe操作内存来添加任务对象
    private void addSubmission(ForkJoinTask<?> t) {
        final ReentrantLock lock = this.submissionLock;
        lock.lock();
        try {
            // 队列只是普通的数组而不是普通线程池的BlockingQueue,
            // 唤醒worker线程的工作由下面的signalWork来完成
            // 使用Unsafe进行内存操作,把任务放置在数组中
            ForkJoinTask<?>[] q; int s, m;
            if ((q = submissionQueue) != null) {
                long u = (((s = queueTop) & (m = q.length-1)) << ASHIFT)+ABASE;
                UNSAFE.putOrderedObject(q, u, t);
                queueTop = s + 1;
                if (s - queueBase == m)
                    // 数组已满,为数组扩容
                    growSubmissionQueue();
            }
        } finally {
            lock.unlock();
        }
        // 通知有新任务来了:两种操作,有空闲线程则唤醒该线程
        // 否则如果可以新建worker线程则为这个任务新建worker线程
        // 如果不可以就返回了,等到有空闲线程来执行这个任务
        signalWork();
    }
接下来要弄清楚就是在compute中fork时,按道理来说这个动作是和主任务在同一个线程中执行,fork是如果把子任务变成多线程执行的:
    public final ForkJoinTask<V> fork() {
        ((ForkJoinWorkerThread) Thread.currentThread())
            .pushTask(this);
        return this;
    }
在上面分析forkOrSubmit的时候同样见到了ForkJoinWorkerThread的pushTask方法调用,那么来看这个方法:
    final void pushTask(ForkJoinTask<?> t) {
        // 代码的基本逻辑和ForkJoinPool的addSubmission方法基本一致
        // 都是把任务加入了任务队列中,这里是加入到ForkJoinWorkerThread
        // 内置的任务队列中
        ForkJoinTask<?>[] q; int s, m;
        if ((q = queue) != null) {    // ignore if queue removed
            long u = (((s = queueTop) & (m = q.length - 1)) << ASHIFT) + ABASE;
            UNSAFE.putOrderedObject(q, u, t);
            queueTop = s + 1;         // or use putOrderedInt
            // 这里不太明白
            if ((s -= queueBase) <= 2)
                pool.signalWork();
            else if (s == m)
                growQueue();
        }
    }
看到这里一下子陷入了僵局,为什么ForkJoinWorkerThread要内建一个队列呢,而且如果子任务仍旧在同一个线程内的话,何以实现并发执行子任务呢?下一篇文章继续。
《java.util.concurrent 包源码阅读》22 Fork/Join框架的初体验的更多相关文章
- 《java.util.concurrent 包源码阅读》 结束语
		
<java.util.concurrent 包源码阅读>系列文章已经全部写完了.开始的几篇文章是根据自己的读书笔记整理出来的(当时只阅读了部分的源代码),后面的大部分都是一边读源代码,一边 ...
 - 《java.util.concurrent 包源码阅读》13 线程池系列之ThreadPoolExecutor 第三部分
		
这一部分来说说线程池如何进行状态控制,即线程池的开启和关闭. 先来说说线程池的开启,这部分来看ThreadPoolExecutor构造方法: public ThreadPoolExecutor(int ...
 - 《java.util.concurrent 包源码阅读》02 关于java.util.concurrent.atomic包
		
Aomic数据类型有四种类型:AomicBoolean, AomicInteger, AomicLong, 和AomicReferrence(针对Object的)以及它们的数组类型, 还有一个特殊的A ...
 - 《java.util.concurrent 包源码阅读》04 ConcurrentMap
		
Java集合框架中的Map类型的数据结构是非线程安全,在多线程环境中使用时需要手动进行线程同步.因此在java.util.concurrent包中提供了一个线程安全版本的Map类型数据结构:Concu ...
 - 《java.util.concurrent 包源码阅读》17 信号量 Semaphore
		
学过操作系统的朋友都知道信号量,在java.util.concurrent包中也有一个关于信号量的实现:Semaphore. 从代码实现的角度来说,信号量与锁很类似,可以看成是一个有限的共享锁,即只能 ...
 - 《java.util.concurrent 包源码阅读》06 ArrayBlockingQueue
		
对于BlockingQueue的具体实现,主要关注的有两点:线程安全的实现和阻塞操作的实现.所以分析ArrayBlockingQueue也是基于这两点. 对于线程安全来说,所有的添加元素的方法和拿走元 ...
 - 《java.util.concurrent 包源码阅读》09 线程池系列之介绍篇
		
concurrent包中Executor接口的主要类的关系图如下: Executor接口非常单一,就是执行一个Runnable的命令. public interface Executor { void ...
 - 《java.util.concurrent 包源码阅读》24 Fork/Join框架之Work-Stealing
		
仔细看了Doug Lea的那篇文章:A Java Fork/Join Framework 中关于Work-Stealing的部分,下面列出该算法的要点(基本是原文的翻译): 1. 每个Worker线程 ...
 - 《java.util.concurrent 包源码阅读》05 BlockingQueue
		
想必大家都很熟悉生产者-消费者队列,生产者负责添加元素到队列,如果队列已满则会进入阻塞状态直到有消费者拿走元素.相反,消费者负责从队列中拿走元素,如果队列为空则会进入阻塞状态直到有生产者添加元素到队列 ...
 
随机推荐
- 多个code.csdn.net账号切换
			
code.csdn.net是国内开源库 使用git需要在项目添加密钥 而如果有多个账户,一个是私人,一个是公司,那么这时怎么做? 密钥存在~/.ssh默认是id_rsa 那么一个比较笨的办法是做一个k ...
 - win10 UWP Controls by function
			
Windows的XAML UI框架提供了很多控件,支持用户界面开发库.其中一些有可视化,一些布局. 一些控件例子:https://github.com/Microsoft/Windows-univer ...
 - HTML5之window.applicationCache对象
			
不知道离线缓存技术的可以参照上一篇文章: HTML5之appcache语法理解/HTML5应用程序缓存/manifest缓存文件官方用法翻译 参考文章 window.applicationCache ...
 - [译]ASP.NET Core 2.0 依赖注入
			
问题 如何使用 ASP.NET Core 服务容器进行依赖注入? 答案 创建一个服务 public interface IGreetingService { string Greet(string t ...
 - SQL Server Alwayson概念总结
			
一.alwayson概念 “可用性组” 针对一组离散的用户数据库(称为“可用性数据库” ,它们共同实现故障转移)支持故障转移环境. 一个可用性组支持一组主数据库以及一至八组对应的辅助数据库(包括一个主 ...
 - Hibernate的一对多查询及去掉重复的对象distinct
			
问:sql 中 select * from A left join B on A.id=B.id where A.id=? 如果在Hibernate 中 用HQL 怎么表达呢 ?答:from A le ...
 - Android———最详细的系统对话框使用
			
在实际应用开发中,用到系统对话框中的情况几乎是没有的.按开发流程来说,UI工程师都会给出每一个弹窗的样式,故而在实际开发中都是自定义弹窗的. 即使用到的地方不多,但是我们也是需要了解并且能熟练的运用它 ...
 - (转)java内存泄漏的定位与分析
			
转自:http://blog.csdn.net/x_i_y_u_e/article/details/51137492 1.为什么会发生内存泄漏 java 如何检测内在泄漏呢?我们需要一些工具进行检测, ...
 - C基本类型
			
C基本类型有: char:8位,可添加修改符signed或是unsigned short:16位,同有singed和unsigned int:32位,同有singed和unsigned long:在3 ...
 - Python - 单步调试
			
Python 有一个单步调试器模块,能实现基本的调试效果!详情请看Python标准文档说明:https://docs.python.org/2/library/pdb.html 调试例子: >& ...