简体   繁体   English

Java 8 ForkJoin无法解释的输出

[英]Java 8 ForkJoin Unexplained Output

I was playing with the ForkJoin framework and Java 8 accumulateAndGet function. 我正在使用ForkJoin框架和Java 8 accumulateAndGet函数。 The following program produced an output that I couldn't explain. 以下程序产生了一个我无法解释的输出。 Can you? 你能?

PS: This is not a homework. PS:这不是作业。 This is an exercise from the book "Java SE 8 for the Really Impatient" by Cay S. Horstmann. 这是由Cay S. Horstmann撰写的“Java SE 8 for the Really Impatient”一书中的练习。 It's a good book. 这是一本好书。

/**
* Q1: Write a program that keeps track of the longest string that is observed by a number of threads. Use an
* {@code AtomicReference} and an appropriate accumulator.
* 
* @param observed
*            Longest string.
* @param x
*            String value to be compared to the longest value.
* @return Longest string.
*/
public static String updateLongestString(final AtomicReference<String> observed, final String x) {
    LOGGER.info("Received observed: {}, x: {}", observed, x);

    final String longestString = observed.accumulateAndGet(x,
        maxBy((str1, str2) -> observed.get().length() - x.length()));

    LOGGER.info("New observed: {}.", longestString);

    return longestString;
}

Unit test: 单元测试:

@Test
public void testUpdateLongestString() {
    final String[] words = new String[] { "Java 8", "Java 8 is Awesome!",
                "Java 8 is the Best thing Since Sliced Bread!", "Java 8 Changes Everything!" };
    final int len = words.length;
    final int stopAfter = 100;

    final AtomicReference<String> longestString = new AtomicReference<>(words[0]);
    final AtomicInteger count = new AtomicInteger(1);

    class UpdateLongestStringTask extends RecursiveAction {
        private static final long serialVersionUID = -2288401002001447054L;

        private int id = -1;

        private UpdateLongestStringTask(final int id) {
            this.id = id;
        }

        @Override
        protected void compute() {
            LOGGER.info("Executing task #: {}.", id);

            if (count.get() >= stopAfter) {
                return;
            }

            final ForkJoinTask<Void> task = new UpdateLongestStringTask(count.incrementAndGet()).fork();

            updateLongestString(longestString, words[randomIndex()]);

            task.join();
        }

        private int randomIndex() {             
            return ThreadLocalRandom.current().nextInt(len);
        }
    }

    /* Just because we can. */
    final int parallelism = min(getRuntime().availableProcessors(), 4);

    new ForkJoinPool(parallelism).invoke(new UpdateLongestStringTask(count.get()));
}

Output (line marked -->> can't be explained; how come it's printing a value it never apparently received): 输出(行标记 - >>无法解释;它是如何打印一个它从未明显收到的值):

2015-01-05 23:20:00.974 [ForkJoinPool-1-worker-1] [INFO ] n.a.j.j.c.PracticeQuestionsCh6Test - Executing task #: 1.
2015-01-05 23:20:00.980 [ForkJoinPool-1-worker-2] [INFO ] n.a.j.j.c.PracticeQuestionsCh6Test - Executing task #: 2.
2015-01-05 23:20:00.980 [ForkJoinPool-1-worker-3] [INFO ] n.a.j.j.c.PracticeQuestionsCh6Test - Executing task #: 3.
2015-01-05 23:20:00.980 [ForkJoinPool-1-worker-0] [INFO ] n.a.j.j.c.PracticeQuestionsCh6Test - Executing task #: 4.
2015-01-05 23:20:00.981 [ForkJoinPool-1-worker-0] [INFO ] n.a.j.j.c.PracticeQuestionsCh6 - Received observed: Java 8, x: Java 8 Changes Everything!
2015-01-05 23:20:00.980 [ForkJoinPool-1-worker-3] [INFO ] n.a.j.j.c.PracticeQuestionsCh6 - Received observed: Java 8, x: Java 8
2015-01-05 23:20:00.980 [ForkJoinPool-1-worker-1] [INFO ] n.a.j.j.c.PracticeQuestionsCh6 - Received observed: Java 8, x: Java 8 is Awesome!
2015-01-05 23:20:00.980 [ForkJoinPool-1-worker-2] [INFO ] n.a.j.j.c.PracticeQuestionsCh6 - Received observed: Java 8, x: Java 8 is the Best thing Since Sliced Bread!
2015-01-05 23:20:01.028 [ForkJoinPool-1-worker-0] [INFO ] n.a.j.j.c.PracticeQuestionsCh6 - New observed: Java 8 Changes Everything!.

-->> 2015-01-05 23:20:01.028 [ForkJoinPool-1-worker-3] [INFO ] n.a.j.j.c.PracticeQuestionsCh6 - New observed: Java 8 Changes Everything!.

2015-01-05 23:20:01.028 [ForkJoinPool-1-worker-1] [INFO ] n.a.j.j.c.PracticeQuestionsCh6 - New observed: Java 8 is Awesome!.

If you provide a function, that function should operate on its arguments, ie 如果你提供一个函数,那么该函数应该对它的参数进行操作,即

final String longestString = observed.accumulateAndGet(x,
    maxBy((str1, str2) -> observed.get().length() - x.length()));

is heavily violating the contract of the functional API. 严重违反了功能API的合同。 accumulateAndGet will provide an atomic update regarding the specified operation but not regarding another get operation within the function. accumulateAndGet将提供有关指定操作的原子更新,但不涉及函数中的另一个get操作。

It's not clear why you did this as implementing the correct function is straight-forward: 目前尚不清楚为什么要这样做,因为实现正确的功能是直截了当的:

final String longestString = observed.accumulateAndGet(x,
    maxBy((str1, str2) -> str1.length() - str2.length()));

or 要么

final String longestString =
    observed.accumulateAndGet(x, maxBy(comparingInt(String::length)));

Note that after fixing the code you still might observe a result different than the two values you have logged before as accumulateAndGet provides you an atomic update but this atomicity does not expand to logging operations you performed before invoking accumulateAndGet . 请注意,在修复代码之后,您仍然可能会观察到与之前记录的两个值不同的结果,因为accumulateAndGet为您提供原子更新,但此原子性不会扩展到您调用accumulateAndGet 之前执行的日志记录操作。 The logged content of the AtomicReference might be outdated at the time when the atomic update is performed. 在执行原子更新时, AtomicReference的记录内容可能已过时。 But due to the contract of the update and your provided operator that resulting String will have at least the same size as the maximum of your previously seen values. 但是由于更新的合同和您提供的运算符,结果String的大小至少与您之前看到的值的最大值相同。

Further, keep in mind that the perceived order of log operations doesn't say anything about the actual order of performed update operations. 此外,请记住,感知的日志操作顺序并未说明执行的更新操作的实际顺序。

You may get a better view of what happens when changing the code as follows: 您可以更好地了解更改代码时发生的情况,如下所示:

public static String updateLongestString(AtomicReference<String> observed, String x) {

    final String longestString = observed.accumulateAndGet(x, maxBy((str1, str2) -> {
        LOGGER.info("Received str1: {}, str2: {}", str1, str2);
        return str1.length() - str2.length();
    }));

    LOGGER.info("New observed: {}.", longestString);

    return longestString;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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