java - 主线程随机未到达末尾(尝试对并发线程中的自然数求和)

标签 java multithreading synchronized memory-visibility

我有以下代码来求 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/

相关文章:

java - 尝试同步两个线程时遇到问题

java - 如何组织 JFrame 上的单选按钮?

java - Android 以编程方式更新 apk

multithreading - Qt中的Qthreadpool

java - 为什么 Thread.stop() 如此危险

java - 为什么这个程序中同一个对象没有死锁 - Java 多线程

java - Hibernate 4.3.4 StandardServiceRegistryBuilder

java - 您认为 Java 中最好的 CMS 是什么

java - 我如何针对单线程/多线程调整它?

Java线程死锁问题