【问题标题】:Thread unsafe decrementing/incrementing - why mostly positive?线程不安全的递减/递增 - 为什么大多是积极的?
【发布时间】:2011-12-12 03:44:24
【问题描述】:

我想知道 java 线程中不安全的递减/递增的结果,所以有我的程序:

主类:

public class Start {

    public static void main(String[] args) {

        int count = 10000000, pos = 0, neg = 0, zero = 0;

        for (int x=0; x<10000; x++) {

            Magic.counter = 0;

            Thread dec = new Thread(new Magic(false, count));
            Thread inc = new Thread(new Magic(true, count));

            dec.start();
            inc.start();

            try {
                inc.join();
                dec.join();
            } catch (InterruptedException e) {
                System.out.println("Error");
            }

            if (Magic.counter == 0)
                zero++;
            else if (Magic.counter > 0)
                pos++;
            else
                neg++;
        }

        System.out.println(Integer.toString(neg) + "\t\t\t" + Integer.toString(pos) + "\t\t\t" + Integer.toString(zero));
    }
}

线程类:

public class Magic implements Runnable {

    public static int counter = 0;

    private boolean inc;
    private int countTo;

    public Magic(boolean inc, int countTo) {
        this.inc = inc;
        this.countTo = countTo;
    }

    @Override
    public void run() {

        for (int i=0;i<this.countTo;i++) {

            if (this.inc)
                Magic.counter++;
            else
                Magic.counter--;
        }

    }
}

我已经运行了几次程序,并且总是得到更多的正面结果而不是负面结果。我也试图改变线程启动的顺序,但这没有改变。一些结果:

Number of results < 0 | Number of results > 0 | Number of results = 0

1103                8893                4
3159                6838                3
2639                7359                2
3240                6755                5
3264                6728                8
2883                7112                5
2973                7021                6
3123                6873                4
2882                7113                5
3098                6896                6

【问题讨论】:

  • 您可能想让 Magic.counter 变量为 volatile,否则操作可能会被重新排序和/或其值可能会按线程缓存在寄存器中。

标签: java multithreading increment decrement


【解决方案1】:

我敢打赌,通过以下更改(即反转分支而不更改任何其他内容),您会看到完全相反的行为:

if (this.inc)
   Magic.counter--; // note change, and lie about `this.inc`
else
   Magic.counter++;

如果为真,这说明线程交互有何意义?

现在,为了好玩,让Magic.counter volatile -- [如何] 结果会发生变化?

删除volatile 并用lock 包围if/else 怎么样? (lock 确保了完整的内存栅栏并建立了一个关键区域。它应该始终产生完美的结果。)

编码愉快。


需要考虑的事项:

  1. 代码仅查看小于或大于零,而不是整体漂移/变化:只需 +1 或 -1 即可使天平倾斜。 (扩展收集的数据可能更有用。)
  2. 执行“else”分支所需的时间稍长,因为需要跳转;通常这不是问题,但超过 1000 万次循环......一两个并不多。
  3. Magic.counter 变量的可见性中,缺少易失性/内存栅栏很多 lee-way。 (我相信符合标准的 JVM 实际上会产生更糟糕的结果......)
  4. ++-- 运算符本质上是非原子的。
  5. 线程交错通常是“不确定的”;如果跨多个内核执行,则更少。

【讨论】:

  • 改变 Magic.counter--/++ 的顺序有点“帮助”,但并不完全相反 :) 它就像 5,5k 负数,4,5k 正数
  • @Nazin 有趣,感谢您的反馈:) 问题之一是 ++ 和 -- 是非原子的,并且线程以这样一种方式交错,即读/写是“拆分” "(wrt。线程之间的操作原子性和值可见性)。 volatile 应该有助于值的可见性,但不足以确保关键区域(因此是原子的)++/-- 操作。
  • @Nazin 也许对[特定] JVM 实现有更复杂细节的人可能知道为什么++-- 在这方面似乎并不完全对称。在这个级别上我能提供的最好的东西是“不确定的”;-)
  • volatile 给了我奇怪的结果,Magic.counter-- 首先(如果)它大约是 4,2k 负数和 5,8k 正数......而且它需要大约 30 倍的时间才能完成:)。 lock 怎么样,我知道它给了我完美的结果,但这不是问题:)
  • @Nazin 来自volatile 的这些数字似乎表明它“更多”是一个非原子问题而不是可见性问题。当然,+1/-1 很容易影响结果,所以……它可能不是一个很好的指标……
【解决方案2】:

一般来说,这是由于 Java 内存模型的工作方式造成的。您正在两个不同的线程中访问共享变量而没有同步。变量既不是声明为 volatile 的,也不是您在运行原子操作。 缺乏协调和 atomar 或 volatile 变量将导致 JVM 在执行时对线程代码进行内部优化。此外,未跨越内存屏障的非易失性变量(即synchronized)将导致每个线程的缓存值——因此在它们的缓存中存在两个冲突的线程本地副本。

鉴于 Java 中没有顺序一致性模型、复杂的运行时优化以及所使用的 JVM 和底层系统(单核或多核、超线程)的特殊性,不可能确定性地预测结果——因为它是违反了 Java 语言模型的几个多线程约定。在同一台机器上运行完全相同的代码可能仍会导致相似的结果,但由于线程调度、其他操作系统进程的 CPU 使用率等的影响,它们可能不会完全相同。

这里有一些关于 JMM 的资源:@​​987654321@

【讨论】:

    猜你喜欢
    • 2015-07-14
    • 1970-01-01
    • 1970-01-01
    • 2012-11-20
    • 2013-10-11
    • 2020-10-10
    • 1970-01-01
    • 2016-08-14
    • 2023-03-12
    相关资源
    最近更新 更多