为什么我没有看到多个线程打印出重复值?

时间:2015-11-15 01:31:45

标签: java multithreading

这很令人尴尬,但我无法想出这个简单的代码。

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
  处理器名称:Intel Core i7
  处理器速度:2.5 GHz
  处理器数量:1
  核心总数:4
  L2缓存(每个核心):256 KB
  L3缓存:6 MB
  内存:16 GB

2 个答案:

答案 0 :(得分:4)

  

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

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

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

通常,具有线程安全问题的程序的实际行为将取决于:

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

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

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

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

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

简而言之,演示存在非线程安全行为可能与演示其缺席一样困难。

答案 1 :(得分:0)

在您的情况下,重复是不可能的,但可能。非确定性是难以测试和观察的。

您可以通过显式复制 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);
        }
    }
}