线程不安全的递减/递增 - 为什么大多是正数?

发布于 2024-12-10 23:56:52 字数 2016 浏览 1 评论 0原文

我想知道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

I'm wondering about result of unsafe decrementing/incrementing in java threads, so there is my program:

Main class:

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));
    }
}

Threads class:

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--;
        }

    }
}

I have run program few times, and always getting much more positive result then negative. I have also tried to change order of which threads starts but this changed nothing. Some results:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

那些过往 2024-12-17 23:56:52

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

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

如果为 true,这可能表明这表明了线程交互的什么?

现在,为了好玩,让 Magic.counter 变得易失性——结果[如何]改变?

删除易失性并用包围if/else怎么样? (确保完整的内存围栏并建立关键区域。它应该总是产生完美的结果。)

快乐的编码。


需要考虑的事项:

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

I bet you will see the exact opposite behavior with the following change (that is, reverse the branches without changing anything else):

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

If true, what might this indicate about this indicate about the thread interactions?

Now, for fun, make Magic.counter volatile -- [how] do the results change?

What about removing volatile and surrounding the if/else with a lock? (A lock ensures a full memory-fence and establishes a critical region. It should always yield perfect results.)

Happy coding.


Things to consider:

  1. The code only looks at less than or greater to zero, not overall drift/variation: a +1 or -1 is all it takes to tip the scales. (It might be more useful to expand the data collected.)
  2. It takes ever so slightly longer to execute an "else" branch as a jump is required; normally this is a non-issue, but over 10 million cycles... one or two isn't much.
  3. Lack of volatile/memory-fence leaves for much lee-way in the visibility of the Magic.counter variable. (I believe a conforming JVM could actually yield far worse results...)
  4. The ++ and -- operators are inherently non-atomic.
  5. Thread interleaving is generally "non-deterministic"; less so if executed across multiple cores.
瑾夏年华 2024-12-17 23:56:52

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

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

以下是有关 JMM 的一些资源:http://www.cs.umd.edu /~pugh/java/memoryModel/

Generally speaking, this is due to the way the Java memory model works. You are accessing shared variables in two distinct threads without synchronization. Neither is the variable declared volatile, nor are you running atomar operations.
The absence of coordination and atomar or volatile variables will lead to internal optimizations of the threaded code done by the JVM when executing. Also, non-volatile variables that have not crossed the memory barrier (i.e. synchronized) will lead to cached values per thread – and thus two conflicting thread-local copies inside their caches.

Given the absence of a sequential consistency model in Java, the complex runtime optimizations and the peculiarities of the used JVM and underlying system(single or multi-core, hyperthreading), it's impossible to predict the result deterministically – just because it is violating several multi-threading conventions for the Java language model. Running the exact same code on the same machine might still lead to similar results, but due to effects of thread scheduling, CPU usage of other OS processes etc. they will not likely be exactly the same.

Here are some resources about the JMM: http://www.cs.umd.edu/~pugh/java/memoryModel/

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文