【问题标题】:Why am I not seeing duplicate values being printed out by multiple threads?为什么我没有看到多个线程打印出重复值?
【发布时间】:2015-11-15 01:31:44
【问题描述】:

这很尴尬,但我无法弄清楚这个简单的代码。

AtomicReferenceTest 启动多个线程,所有线程都引用同一个 id 生成器。

IdGenerator 没有线程安全性并维护一个由多个线程递增的变量,因此我完全希望线程打印出其他线程已经报告的重复值,但这不是我所看到的.

当我打印出值时,我看不到重复值,但线程似乎看到其他线程更新的值。

这怎么可能,我错过了什么?

public class AtomicReferenceTest {
    public static void main(String args[]) throws InterruptedException {
        AtomicReferenceTest.lockingIdGenerator();
    }

    public static void lockingIdGenerator() throws InterruptedException {
        // Change the value in the first parameter to allow more threads to run simultaneously
        IdGenerator idGenerator = new OneIdThreadGenerator();
        ExecutorService tpe = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 10; i++) {
            tpe.execute(new Runnable() {
                long id = 0;

                @Override
                public void run() {
                    final long threadId = Thread.currentThread().getId();
                    System.out.println("Value of id in " + threadId + " is = " + id);
                    while (id < 1000) {
                        id = idGenerator.nextId();
                        System.out.println(threadId + " : " + id);
                    }
                    System.out.println("Stopping thread : " + threadId);
                }
            });
        }
        tpe.shutdown();
        tpe.awaitTermination(1, TimeUnit.DAYS);
    }
}

OneIdThreadGenerator:

/**
 * This class has no thread safety at all
 */
class OneIdThreadGenerator implements IdGenerator{
    private long id = 0;
    public long nextId() {
        id = id + 1;
        return id;
    }
}

我的系统规格是:

型号名称:MacBook Pro
处理器名称:英特尔酷睿 i7
处理器速度:2.5 GHz
处理器数量:1
核心总数:4
L2 缓存(每个核心):256 KB
L3 缓存:6 MB
内存:16 GB

【问题讨论】:

  • 有趣的问题,但只是为了确定;您能否添加您的环境的简短摘要(CPU 架构;执行单元的数量,即 CPU 内核和 SMT(如果适用);JRE 或 JDK 的供应商和版本)?
  • 我自己运行了这段代码,实际上看到了很多重复...并且某些线程的 id 值高达 1009
  • JVM 调度程序可能很奇怪 :P 它有时可以决定表现得很好并提供线程安全的行为,但不要指望它......
  • @tixopi 您的评论具有误导性; JVM 调度程序的行为方式应该与 JMM 允许它在线程安全方面的行为方式相同。在这种情况下,OP 可能不会像她预期的那样看到错误,但 JMM 确实允许重复值。
  • @fge 抱歉,是的,我当然不是说 JVM 会一直这样。这只是一个轻松的评论,试图预测调度程序的行为可能是徒劳的:P

标签: java multithreading


【解决方案1】:

这怎么可能,我错过了什么?

我认为您的错误是认为存在线程安全问题的程序在您运行时会(总是)显示这些问题。

JVM 规范的编写方式,如果您按照内存模型部分中规定的规则编写程序,则可以保证它在所有实现规范的 Java 平台上(正确地)表现出可预测的行为。如果你不这样做,那么你就没有那个保证。但相反,规范并没有说应用程序表现出意外。

一般来说,存在线程安全问题的程序的实际行为将取决于:

  • 运行程序的内核数量将决定是否存在真正的并行执行或伪并行(特定于平台),

  • JIT 编译器如何将您的字节码编译为本机代码(可能特定于 JVM 版本),

  • 两个线程在短时间内不安全地尝试使用相同共享状态的概率(特定于应用程序),以及

  • 相当程度的“运气”;即随机/不可预测因素的影响。

如果只使用一个内核,那么任何伪并行都将是线程调度程序抢占一个线程以允许另一个线程运行的结果。当这种情况发生时,将会有一个隐含的“内存屏障”,导致内存缓存被刷新。这将消除不可预测性的主要来源。


简而言之,证明存在非线程安全行为可能与证明其不存在一样困难。

【讨论】:

  • 是的,这似乎确实如此。我增加了编号。次 nextId() 被调用到 10000 有 100 个线程,我可以看到有 4 个重复创建。然后我增加到 500 个线程和 100000 个,只创建了 3 个。看来这台 Mac 的行为与我以前使用的以前的 Windows 笔记本电脑和机器不同,因为在我以前的机器中证明非线程安全代码非常容易,只需 100 次迭代和 10 个线程。
【解决方案2】:

在您的情况下不太可能出现重复,但有可能。不确定性很难测试和观察。

您可以通过显式复制 id 并扩大增量和返回之间的时间窗口来模拟处于 >++ 操作:

/**
 * This class has no thread safety at all
 */
class OneIdThreadGenerator implements IdGenerator{
    private long id = 0;
    public long nextId() {
        try {
            long nextId = id + 1;
            Thead.sleep(1000);
            id = nextId;
            return nextId;
        } catch (InterruptedException) {
            throw new RuntimeException(e);
        }
    }
}

【讨论】:

    猜你喜欢
    • 2019-01-26
    • 2020-06-18
    • 2017-06-13
    • 1970-01-01
    • 2021-01-05
    • 1970-01-01
    • 2020-03-15
    • 2017-12-23
    • 1970-01-01
    相关资源
    最近更新 更多