简体   繁体   中英

In Java's ForkJoinTask, does the order of fork/join matter?

Let's say we extended a RecursiveTask called MyRecursiveTask .

Then two sub-tasks are created within the scope of a forkJoinTask:

MyRecursiveTask t1 = new MyRecursiveTask()
MyRecursiveTask t2 = new MyRecursiveTask()
t1.fork()
t2.fork()

I think then the "t2" will be at the top of the Workqueue (which is a deque, it is used as a stack for the worker itself), as I saw the implementation for fork method like this:

public final ForkJoinTask<V> fork() {
    Thread t;
    if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
        ((ForkJoinWorkerThread)t).workQueue.push(this);
    else
        ForkJoinPool.common.externalPush(this);
    return this;
}

if so, is there any difference for performance for the two expressions below:

Expression1:

t1.join() + t2.join()

Expression2:

t2.join() + t1.join()

I think it may matter. t1.join() will always be blocking (if there is no work stealing) before t2.join() finished, because only the task at the top of workQueue can be poped. (In other words, t2 has to be poped before t1 is poped). Below is the codes for doJoin and tryUnpush .

private int doJoin() {
    int s; Thread t; ForkJoinWorkerThread wt; ForkJoinPool.WorkQueue w;
    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();
}

/**
 * Pops the given task only if it is at the current top.
 * (A shared version is available only via FJP.tryExternalUnpush)
*/
final boolean tryUnpush(ForkJoinTask<?> t) {
    ForkJoinTask<?>[] a; int s;
    if ((a = array) != null && (s = top) != base &&
        U.compareAndSwapObject
        (a, (((a.length - 1) & --s) << ASHIFT) + ABASE, t, null)) {
        U.putOrderedInt(this, QTOP, s);
        return true;
    }
    return false;
}

Does anyone have ideas about this? Thanks!

Whether your using Java7 or Java8 is important. In Java7 the framework creates continuation threads for join(). In Java8 the framework mostly stalls for join(). See here. I've been writing a critique about this framework since 2010.

The recommendation for using a RecursiveTask (from the JavaDoc):

return f2.compute() + f1.join();

This way the splitting thread continues the operation itself.

Relying on the F/J code for direction is not recommended since this code changes frequently. For instance, in Java8 using nested parallel streams caused too many compensation threads that the code was reworked in Java8u40 only to cause more problems. See here .

If you must do multiple joins, then it really doesn't matter what order you join(). Each fork() makes the task available for any thread.

If you have enough core counts than both threads run in parallel, it doesn't matter which one is started first because completition time matters. So, whichever one finishes the first has to wait for the other to finish and calculate the result. If you have 1 core than what you're thinking is could be true, but for 1 core why do you need to parallelize the job?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM