我有以下代码来求 1 到 5000 之间的自然数之和。这是一个练习并发的简单练习。
public static void main(String[] args) throws InterruptedException {
final int[] threadNb = new int[] {5};
final Integer[] result = new Integer[1];
result[0] = 0;
List<Thread> threads = new LinkedList<>();
IntStream.range(0, threadNb[0]).forEach(e -> {
threads.add(new Thread(() -> {
int sum = 0;
int idx = e * 1000 + 1;
while (!Thread.interrupted()) {
if (idx <= (e + 1) * 1000) {
sum += idx++;
} else {
synchronized(result) {
result[0] += sum;
System.err.println("sum found (job " + e + "); sum=" + sum + "; result[0]=" + result[0] + "; idx=" + idx);
Thread.currentThread().interrupt();
}
}
}
synchronized(result) {
System.err.println("Job " + e + " done. threadNb = " + threadNb[0]);
threadNb[0]--;
System.err.println("threadNb = " + threadNb[0]);
}
}));
});
threads.forEach(Thread::start);
//noinspection StatementWithEmptyBody
while(threadNb[0] > 0);
System.out.println("begin result");
System.out.println(result[0]);
System.out.println("end result");
}
有时,当我运行代码时,最后 3 System.out.println()
不显示。如果我在 while(threadNb[0] > 0)
中发表声明,像另一个 System.out.println()
,我的问题再也不会发生了。
谁能给我解释一下这种行为吗?
预先感谢您的帮助
最佳答案
关于 threadNb 变量的声明方式,没有任何内容告诉 JVM 它需要对其进行更新以使其对其他线程可见。变量的更新何时对其他线程可见完全取决于 JVM 实现,它可以根据情况使它们可见或不可见。此外,如果 JIT 认为可以逃脱惩罚,它可以自由地重新排序或优化代码,并且它的决策基于可见性规则。因此,很难准确地说出这里发生了什么,因为 Java 语言规范未指定该行为,但肯定会遇到一个问题,即主线程通常看不到工作线程的更新。
如果用 AtomicInteger 替换数组,则保证更新对其他线程可见。 (Volatile 也可以,但首选 AtomicInteger。为了使用 volatile,您必须使变量成为实例或类成员。)如果将更新的值保存在局部变量中,则不需要同步:
import java.util.*;
import java.util.stream.*;
import java.util.concurrent.atomic.*;
public class SumNumbers {
public static void main(String[] args) throws InterruptedException {
AtomicInteger threadNb = new AtomicInteger(5);
AtomicInteger result = new AtomicInteger(0);
List<Thread> threads = new LinkedList<>();
IntStream.range(0, threadNb.intValue()).forEach(e -> {
threads.add(new Thread(() -> {
int sum = 0;
int idx = e * 1000 + 1;
while (!Thread.currentThread().isInterrupted()) {
if (idx <= (e + 1) * 1000) {
sum += idx++;
} else {
int r = result.addAndGet(sum);
System.out.println("sum found (job " + e + "); sum="
+ sum + "; result=" + r
+ "; idx=" + idx);
Thread.currentThread().interrupt();
}
}
System.out.println("Job " + e + " done.");
int threadNbVal = threadNb.decrementAndGet();
System.out.println("Job " + e + " done, threadNb = " + threadNbVal);
}));
});
threads.forEach(Thread::start);
//noinspection StatementWithEmptyBody
while(threadNb.intValue() > 0);
System.out.println("result=" + result.intValue());
}
}
您可以在其中看到更新变得可见。
不建议使用忙等待,因为它会浪费 CPU 周期。你确实在无锁编程中看到了它,但这在这里并不是一件好事。 Thread#join 可以工作,或者您可以使用 CountdownLatch。
请注意,使用Thread#interrupted()
会清除中断标志。通常当你要抛出 InterruptedException 时使用它,否则最好使用 Thread.currentThread().isInterrupted() 。在这种特定情况下它不会造成任何伤害,因为 while 循环测试是唯一使用该标志的东西,因此它是否被清除是无关紧要的。
关于java - 主线程随机未到达末尾(尝试对并发线程中的自然数求和),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32523459/