代码优先:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.LongAccumulator;
import java.util.function.LongBinaryOperator;
import java.util.stream.IntStream;
/**
* Created by tom on 17-4-13.
*/
public class ForSOF {
public static void main(String[] args){
LongBinaryOperator op = (v, y) -> (v*2 + y);
LongAccumulator accumulator = new LongAccumulator(op, 1L);
ExecutorService executor = Executors.newFixedThreadPool(2);
IntStream.range(0, 10)
.forEach(i -> executor.submit(() -> accumulator.accumulate(i)));
stop(executor);
// 2539 expected, however result does not always be! I had got 2037 before.
System.out.println(accumulator.getThenReset());
}
/**
* codes for stop the executor, it's insignificant for my issue.
*/
public static void stop(ExecutorService executor) {
try {
executor.shutdown();
executor.awaitTermination(60, TimeUnit.SECONDS);
}
catch (InterruptedException e) {
System.err.println("termination interrupted");
}
finally {
if (!executor.isTerminated()) {
System.err.println("killing non-finished tasks");
}
executor.shutdownNow();
}
}
}
以上是关于测试LongAccumulator类Java 8的崩溃的全部代码。来自博客winterbe.com的代码以及关于java8的好旅游文章。
正如API文档所说,包java.util.concurrent.atomic
中那些类的方法将是原子的和线程安全的。但是,当我运行上述内容时,肯定会有结果。有时候会出现2037年或1244年的错误。一定有什么问题。
当我将主体内的第一行重写为:
LongBinaryOperator op = (v, y) -> (v + y);
它以正确的方式运作。
所以,我猜想当JVM得到v并加倍时,其他线程有中断并获得相同的v值,计算op表达式并给出一个结果,该结果很快就被前线程重写。这就是表达v + y的原因。
如果应该改进这些代码中的任何一个? 还是LongAccumulator类的错误? 我不确定。
天啊!这些代码的逻辑应该是这样的:
public void test(){
int v = 1;
for (int i = 0; i < 10; i++) {
v = x(v, i);
}
System.out.println(v);
}
int x(int v, int y){
return v*2+y;
}
,v的输出是2037。
所以让我困惑的一定是计算流的混乱。
答案 0 :(得分:5)
正如API文档所说,java.util.concurrent.atomic包中的那些类的方法将是原子和线程安全的。但是,当我运行上述内容时,肯定会有结果。 2037年或1244年的时间错误。必定有问题。
LongAccumulator
docs提供了一条重要信息。
线程内或线程之间的累积顺序无法保证且不能依赖,因此该类仅适用于累积顺序无关紧要的函数。
您的问题是v + y
操作是noncommutative。如果混合操作的顺序,您可以获得各种输出。 List<Long> list = new ArrayList<>();
for (long i = 0; i < 10; i++) {
list.add(i);
}
// if you uncomment this, you get varying results
// Collections.shuffle(list);
long total = 1;
for (Long i : list) {
total = total * 2 + i;
}
System.out.println(total);
有效的原因是您可以按任何顺序应用它并获得相同的结果。
要将其分解为串行代码,您实际上是这样做的:
Collections.shuffle(...)
有序结果是2037,但是如果你取消注释if(string.equals(string2) == true);
,那么你会发现当数字的顺序相反时,结果会因最大值9218而变化。
答案 1 :(得分:2)
更新:下面描述的问题已被接受为错误:JDK-8178956
由于这是一个关于LongAccumulator
是否有错误的问题,我想指出它是,或者更确切地说文档可能是错误的。
首先,文件明确指出:
线程内或线程之间的累积顺序无法保证且不能依赖,因此该类仅适用于累积顺序无关紧要的函数。
问题中使用的函数((v, y) -> (v*2 + y)
)明显违反了该函数,因为调用accumulate()
的顺序很重要。
如果我们在数学形式中添加更多内容,如果我们累积了两个值a1
和a2
,那么文档会明确指出结果可以是以下任何一种:
result = f( f(identity, a1), a2)
result = f( f(identity, a2), a1)
文档还说明:
提供的累加器功能应该是无副作用的,因为当尝试的更新由于线程之间的争用而失败时,它可能会被重新应用。
这意味着f(identity, a1)
可能会被调用两次,但其中一个调用的结果将被丢弃。或者调用了f(identity, a1)
和f(identity, a2)
,但是只使用了其中一个,如上所示,另一个被丢弃。
但是,文档说:
该函数应用当前值作为其第一个参数,并将给定更新作为第二个参数。
上面显示的是错误,因为结果也可能像这样计算,明显违反了最后一个声明:
result = f( identity, f(a1, a2) )
其中内部调用的第一个参数是而不是&#34;当前值&#34;累加器,外部调用的第二个参数是不&#34;给定更新&#34;值。
为了看到这一点,我将问题代码修改为:
LongBinaryOperator op = (v, y) -> {
try { Thread.sleep(200); } catch (InterruptedException ignored) {}
long result = (v + y);
System.out.printf("f(%d, %d) = %d%n", v, y, result);
return result;
};
LongAccumulator accumulator = new LongAccumulator(op, 1L);
ExecutorService executor = Executors.newFixedThreadPool(2);
Arrays.asList(2, 3, 5, 11, 17, 23, 29, 37, 41, 47).stream()
.forEach(i -> executor.submit(() -> accumulator.accumulate(i)));
executor.shutdown();
executor.awaitTermination(60, TimeUnit.SECONDS);
System.out.println(accumulator.getThenReset());
运行它会产生这个结果(每次执行时结果都不同):
f(1, 3) = 4
f(1, 2) = 3
f(2, 11) = 13
f(4, 5) = 9
f(13, 17) = 30
f(23, 29) = 52
f(30, 37) = 67
f(52, 41) = 93
f(67, 47) = 114
f(9, 93) = 102
f(102, 114) = 216
216
为了说明如何调用函数来计算结果,我将通过对值应用名称对其进行注释,即id
的{{1}}的初始身份值,{{1} } 1
表示累积(输入)值,a0
表示a9
表示函数结果。请注意,r1
被丢弃,这就是为什么有11个调用只有10个值。
r11
答案 2 :(得分:-1)
您必须更改代码,如下所示:
public static void main(String[] args) {
LongBinaryOperator op = (v, y) -> (v + 2*y);
LongAccumulator accumulator = new LongAccumulator(op, 1L);
ExecutorService executor = Executors.newFixedThreadPool(4);
IntStream.range(0, 100)
.mapToObj(val -> executor.submit(() -> accumulator.accumulate(val)))
.forEach(future -> { while (!future.isDone()); });
executor.shutdown();
System.out.println(accumulator.get());
}
首先它使op
执行顺序独立,因为v
表示已计算值的总和,y
表示要为其计算新值的IntStream
的值2*y
:
LongBinaryOperator op = (v, y) -> (v + 2*y);
其次,你必须等待每个提交的任务完成,这可以通过检查返回的Future
来检测到:
forEach(future -> { while (!future.isDone()); });
现在它应该稳定运行。