java 7 forkjoin并行框架的源码详究

          forkjoin是java 7中的一个并行的线程执行框架,它最大的特点是能够把任务进行分割,这些分割之后的任务,是相互独立的,互不影响。当然分割这些任务的粒度是可以自己控制的,而且分割任务粒度的逻辑是必不可少的,比如说:你设定的粒度(阈值)为10 那么,你有一个大任务是100,那么你分得分割10次,而且分割时递归式的分割,每个任务是10,有10个任务。这10个任务是相互独立的。正是因为,任务是相互独立性的这个特定,使得在资源竞争比较激烈的并行框架中,使用forkjoin最为合适,它很好的解决了多线程资源竞争资源,出现的问题。这里,说的竞争资源,是cup资源。

          forkjoin这个框架主要的线程池类是ForkJoinPool,它继承了AbstractExecutorService这并行框架抽象类。细心的读者可能会发现,在java 5中的并行框架ExecutorService的实现类ThreadPoolExecutor线程池类也继承了AbstractExecutorService。所以我们可以说forkjoin框架是java并行框架中的一个实现。我们也可以得出一个结论:AbstractExecutorService是并行线程池的抽象父类。说了那么多废话,那我们开始进入正题吧。

        让我们看看ForkjoinPool这个类的主要构造方法里面做了些什么事情。

         checkPermission();//坚持虚拟机是否有创建线程的权限
        if (factory == null) //判断创建线程的线程工厂是否为null,否则抛出一个异常
            throw new NullPointerException();
        if (parallelism <= 0 || parallelism > MAX_ID)
            throw new IllegalArgumentException();
        this.parallelism = parallelism;//线程的并行度
        this.factory = factory;//创建线程的线程工厂
        this.ueh = handler;
        this.locallyFifo = asyncMode;
        long np = (long)(-parallelism); // offset ctl counts
        this.ctl = ((np << AC_SHIFT) & AC_MASK) | ((np << TC_SHIFT) & TC_MASK);

       //创建一个任务队列,默认大小是8
        this.submissionQueue = new ForkJoinTask[INITIAL_QUEUE_CAPACITY];
        // initialize workers array with room for 2*parallelism if possible
        int n = parallelism << 1;

       //下面一段if代码很有意思,它会根据你传进来的并发度的大小,创建一个比并发度大小大2-3倍+1的线程池数组。

        if (n >= MAX_ID)
            n = MAX_ID;
        else { // See Hackers Delight, sec 3.2, where n < (1 << 16)
            n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8;
        }
        workers = new ForkJoinWorkerThread[n + 1];//创建线程池数组

      //下面的两行代码就不用我多说了吧,相信大家都知道,至于有什么作用我稍后细说。
        this.submissionLock = new ReentrantLock();
        this.termination = submissionLock.newCondition();

     //下面几行代码类似于日子功能,描述工作线程有多少个,一般只有一个。
         StringBuilder sb = new StringBuilder("ForkJoinPool-");
        sb.append(poolNumberGenerator.incrementAndGet());
        sb.append("-worker-");
        this.workerNamePrefix = sb.toString();

      当外面创建一个ForkJoinPool对象之后,一般多会调用invoke,execute,submit等这些方法,这些方法他们都有类似的作用。作用是,首先会这些方法都会接受一个或多个任务(有无返回结果的任务),然后创建一个线程,并把这个线程加入大线程中,启动线程,调用任务的compute方法。接着我们来说说任务类吧。

     RecursiveAction,RecursiveTask这两个类分别是无返回结果,和有结果返回的任务类,他们有一个主要的compute方法这个方法是用户自己实现业务逻辑的地方。这两个任务类都继承了ForkJoinTask这个主要的任务类,它有一个主要方法doExec会调用 exec这个方法,这个方法会调用用户的compute的方法,所以在forkjoin框架里面启动线程执行任务,它会调用doExec()这个方法。好了 逻辑讲完了我们来看看invoke,execute,submit的源码吧,我只抽取一个方法源码进行详细分析。

     

1》public void execute(ForkJoinTask task) {
        if (task == null) //判断传进来的任务是否为空,为Null直接抛出异常
            throw new NullPointerException();
        forkOrSubmit(task);//
    }


2》  private void forkOrSubmit(ForkJoinTask 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(task);  //如果是主线程那么将任务放到主线程池forkjoin中去。
    }


 3》 final void pushTask(ForkJoinTask t) {
        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)//判断是否到了任务队列顶部,如果没有这调用signalWork这个方法会创建一个线程
                pool.signalWork();
            else if (s == m)
                growQueue();  //如果判断到了任务队列的顶部,那么久会扩容
        }
    }      


 3》private void addSubmission(ForkJoinTask t) {
        final ReentrantLock lock = this.submissionLock;//当前的forkjoinpool对象的锁
        lock.lock();
        try {
            ForkJoinTask[] q; int s, m;

        //下面一段代码回判断提交任务的个数是否超过了线程任务的大小。如果超过就扩容
            if ((q = submissionQueue) != null) {    // ignore if queue removed
                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();
        }
        signalWork();
    }

4》 final void signalWork() {
        /*
         * The while condition is true if: (there is are too few total
         * workers OR there is at least one waiter) AND (there are too
         * few active workers OR the pool is terminating).  The value
         * of e distinguishes the remaining cases: zero (no waiters)
         * for create, negative if terminating (in which case do
         * nothing), else release a waiter. The secondary checks for
         * release (non-null array etc) can fail if the pool begins
         * terminating after the test, and don't impose any added cost
         * because JVMs must perform null and bounds checks anyway.
         */
        long c; int e, u;
        while ((((e = (int)(c = ctl)) | (u = (int)(c >>> 32))) &
                (INT_SIGN|SHORT_SIGN)) == (INT_SIGN|SHORT_SIGN) && e >= 0) {
            if (e > 0) {                         // release a waiting worker
                int i; ForkJoinWorkerThread w; ForkJoinWorkerThread[] ws;
                if ((ws = workers) == null ||
                    (i = ~e & SMASK) >= ws.length ||
                    (w = ws[i]) == null)
                    break;
                long nc = (((long)(w.nextWait & E_MASK)) |
                           ((long)(u + UAC_UNIT) << 32));
                if (w.eventCount == e &&
                    UNSAFE.compareAndSwapLong(this, ctlOffset, c, nc)) {
                    w.eventCount = (e + EC_UNIT) & E_MASK;
                    if (w.parked)
                        UNSAFE.unpark(w);
                    break;
                }
            }
            else if (UNSAFE.compareAndSwapLong
                     (this, ctlOffset, c,
                      (long)(((u + UTC_UNIT) & UTC_MASK) |
                             ((u + UAC_UNIT) & UAC_MASK)) << 32)) {
                addWorker();//关键是这段代码,其他的代码我不是很清楚请见谅。
                break;
            }
        }
    }

5》

  private void addWorker() {
        Throwable ex = null;
        ForkJoinWorkerThread t = null;
        try {
            t = factory.newThread(this);//用线程工厂创建一个线程
        } catch (Throwable e) {
            ex = e;
        }
        if (t == null) {  // null or exceptional factory return
            long c;       // adjust counts
            do {} while (!UNSAFE.compareAndSwapLong
                         (this, ctlOffset, c = ctl,
                          (((c - AC_UNIT) & AC_MASK) |
                           ((c - TC_UNIT) & TC_MASK) |
                           (c & ~(AC_MASK|TC_MASK)))));
            // Propagate exception if originating from an external caller
            if (!tryTerminate(false) && ex != null &&
                !(Thread.currentThread() instanceof ForkJoinWorkerThread))
                UNSAFE.throwException(ex);
        }
        else
            t.start();//启动线程
    }

6》

     //这个方法是用线程工厂创建的也是就上面  t = factory.newThread(this);这一段代码

     public ForkJoinWorkerThread newThread(ForkJoinPool pool) {
            return new ForkJoinWorkerThread(pool);
        }

7》

//当第5阶段调用start方法之后,就会执行这个run方法

 public void run() {
        Throwable exception = null;
        try {
            onStart();
            pool.work(this);//这是将当前线程加入到线程池中去,
        } catch (Throwable ex) {
            exception = ex;
        } finally {
            onTermination(exception);
        }
    }

8》

  final void work(ForkJoinWorkerThread w) {
        boolean swept = false;                // true on empty scans
        long c;
        while (!w.terminate && (int)(c = ctl) >= 0) {
            int a;                            // active count
            if (!swept && (a = (int)(c >> AC_SHIFT)) <= 0)
                swept = scan(w, a);//这是主要方法
            else if (tryAwaitWork(w, c))
                swept = false;
        }
    }


9》

 private boolean scan(ForkJoinWorkerThread w, int a) {
        int g = scanGuard; // mask 0 avoids useless scans if only one active
        int m = (parallelism == 1 - a && blockedCount == 0) ? 0 : g & SMASK;
        ForkJoinWorkerThread[] ws = workers;
        if (ws == null || ws.length <= m)         // staleness check
            return false;
        for (int r = w.seed, k = r, j = -(m + m); j <= m + m; ++j) {
            ForkJoinTask t; ForkJoinTask[] q; int b, i;
            ForkJoinWorkerThread v = ws[k & m];
            if (v != null && (b = v.queueBase) != v.queueTop &&
                (q = v.queue) != null && (i = (q.length - 1) & b) >= 0) {
                long u = (i << ASHIFT) + ABASE;
                if ((t = q[i]) != null && v.queueBase == b &&
                    UNSAFE.compareAndSwapObject(q, u, t, null)) {
                    int d = (v.queueBase = b + 1) - v.queueTop;
                    v.stealHint = w.poolIndex;
                    if (d != 0)
                        signalWork();             // propagate if nonempty  这个方法我们很熟悉了吧,就是创建一个线程执行任务
                    w.execTask(t);//这个方法是重点
                }
                r ^= r << 13; r ^= r >>> 17; w.seed = r ^ (r << 5);
                return false;                     // store next seed
            }
            else if (j < 0) {                     // xorshift
                r ^= r << 13; r ^= r >>> 17; k = r ^= r << 5;
            }
            else
                ++k;
        }
        if (scanGuard != g)                       // staleness check
            return false;
        else {                                    // try to take submission
            ForkJoinTask t; ForkJoinTask[] q; int b, i;
            if ((b = queueBase) != queueTop &&
                (q = submissionQueue) != null &&
                (i = (q.length - 1) & b) >= 0) {
                long u = (i << ASHIFT) + ABASE;
                if ((t = q[i]) != null && queueBase == b &&
                    UNSAFE.compareAndSwapObject(q, u, t, null)) {
                    queueBase = b + 1;
                    w.execTask(t);
                }
                return false;
            }
            return true;                         // all queues empty
        }
    }

10》


 final void execTask(ForkJoinTask t) {
        currentSteal = t;
        for (;;) {
            if (t != null)
                t.doExec();//这个方法我们很熟悉吧,它会调用exec()()这个方法
            if (queueTop == queueBase)
                break;
            t = locallyFifo ? locallyDeqTask() : popTask();
        }
        ++stealCount;
        currentSteal = null;
    }

11》

  final void doExec() {
        if (status >= 0) {
            boolean completed;
            try {
                completed = exec();//这个方法会返回任务状态
            } catch (Throwable rex) {
                setExceptionalCompletion(rex);
                return;
            }
            if (completed)
                setCompletion(NORMAL); // must be outside try block
        }
    }

12》

  protected final boolean exec() {
        result = compute();//这个方法 终于是我们最主要的方法,用户自定义的方法,实现。 
        return true;
    }

以上12个阶段,真是太绕了,其实我们主要关心compute方法什么时候被调用。我们理清下思路,forkjoinPool线程池对象,它自己有个任务队列默认是8。这个线程池有一个任务队列,这个队列放原始任务的,也就是说大任务 比如说:一个任务为40,任务粒度为10 那么一共可以分为4个任务,也就是说这个任务队列会放,大任务为40。这个大任务,是由主线程(main)线程去创建一个ForkJoinWorkerThread类型的线程,由这个线程去执行第一个任务,也就是40这个任务,发现40这个任务的粒度不满足,继续分割,分割之后,你需要调用fork和join方法,让ForkJoinWorkerThread,去创建一个子线程,这个过程是递归的过程。总而言之,大概的逻辑是这样的。这是一部分,我之后还会对其他的方法做深入研究。

     

       

你可能感兴趣的:(java 7 forkjoin并行框架的源码详究)