【问题标题】:What is the difference between volatile and synchronized?volatile 和 synchronized 有什么区别?
【发布时间】:2016-03-15 21:52:58
【问题描述】:

我想看看volatile 在这里是如何工作的。如果我将cc 声明为volatile,我会得到下面的输出。我知道线程执行输出不时变化,但我在某处读到volatilesynchronized 相同,那么为什么我会得到这个输出?如果我使用两个 Thread1 实例,这有关系吗?

2线程-0 2Thread-1 4Thread-1 3线程-0 5Thread-1 6线程-0 7Thread-1 8线程-0 9Thread-1 10线程-0 11Thread-1 12线程-0
public class Volexample {
    int cc=0;

    public static void main(String[] args) {
        Volexample ve=new Volexample();
        CountClass count =ve.new  CountClass();
        Thread1 t1=ve.new Thread1(count);
        Thread2 t2=ve.new Thread2(count);
        t1.start();
        t2.start();
    }

    class Thread1 extends Thread{
        CountClass count =new  CountClass();

        Thread1(CountClass count ){
            this.count=count;
        }

        @Override
        public void run() {
            /*for(int i=0;i<=5;i++)
            count.countUp();*/
            for(int i=0;i<=5;i++){
                cc++;
                System.out.println(cc + Thread.currentThread().getName());
            }
        }
    }

    class Thread2 extends Thread {
        CountClass count =new  CountClass();

        Thread2(CountClass count ){
            this.count=count;
        }

        @Override
        public void run(){
            /*for(int i=0;i<=5;i++)
            count.countUp();*/
            for(int i=0;i<=5;i++){
                cc++;
                System.out.println(cc + Thread.currentThread().getName());
            }
        }
    }

    class CountClass{
        volatile int count=0;
        void countUp(){
            count++;
            System.out.println(count + Thread.currentThread().getName());
        }
    }
}

【问题讨论】:

  • volatilesynchronized 不同:volatile 提供内存可见性,但不提供原子性; synchronized 两者都有。
  • 你期望什么输出?为什么对count.countUp() 的调用被注释掉了?您如何访问 volatile 变量?
  • o/p 是什么意思?
  • volatile 确实使 count++ 原子化。如果将其更改为 AtomicInteger,它将是原子的而不使用同步。
  • 感谢您的即时响应,但我仍然有些困惑...... 1.正如我之前发布的那样,我在该类中有一个数据成员 cc,我有 2 个差异线程类 Thread1 和 Thread2 所以如果我使该数据成员 cc 易失,然后如您所见,上面的输出(o / p) 2 diff 线程已产生输出:: 2Thread-0 2Thread-1 因此,如果 volatile 反映主内存中的更改而不是线程内存中的更改,那么这怎么可能?2。如果我使用同一类 Thread1 类的 2 个不同线程,或者如果我使用 2 个 diff 线程类的 2 个线程(如 Thread1 和 Thread 2)之间是否存在差异?

标签: java multithreading synchronized volatile java-memory-model


【解决方案1】:

在 Java 中,volatile 关键字的语义定义非常明确。它们确保其他线程将看到变量的最新更改。但它们不会使读-修改-写操作成为原子操作。

所以,如果ivolatile 并且您执行i++,则可以保证读取i 的最新值,并且可以保证其他线程会立即看到您对i 的写入,但是您不能保证两个线程不会交错它们的读/修改/写操作,因此两个增量具有单个增量的效果。

假设i 是一个可变整数,其值被初始化为零,除此之外没有发生任何写入,并且两个线程执行i++;,可能会发生以下情况:

  1. 第一个线程读取一个零,最新值为i
  2. 第二个线程读取零,也是i 的最新值。
  3. 第一个线程增加它读取的零,得到一个。
  4. 第二个线程增加它读取的零,也得到一。
  5. 第一个线程将其计算的结果写入i
  6. 第二个线程将它计算出的写入i
  7. 写入i 的最新值为1,因此现在访问i 的任何线程都会看到1。

请注意,一个增量丢失了,即使每个线程总是读取任何其他线程写入的最新值。 volatile 关键字提供可见性,而不是原子性。

您可以使用synchronized 来形成复杂的原子操作。如果你只需要简单的,你可以使用Java提供的各种Atomic*类。

【讨论】:

  • 或AtomicInteger、AtomicLong等
  • 感谢您的即时响应,但我仍然有些困惑...... 1.正如我之前发布的那样,我在该类中有一个数据成员 cc,我有 2 个差异线程类 Thread1 和 Thread2 所以如果我使该数据成员 cc 易失,然后如您所见,上面的输出(o / p) 2 diff 线程已产生输出:: 2Thread-0 2Thread-1 因此,如果 volatile 反映主内存中的更改而不是线程内存中的更改,那么这怎么可能?2。如果我使用同一类 Thread1 类的 2 个不同线程,或者如果我使用 2 个 diff 线程类的 2 个线程(如 Thread1 和 Thread 2)之间是否存在差异?
  • volatile 关键字与“线程内存”无关。这是一个神话(请参阅我对 The Stigger 的 cmets)。我在上面的回答中解释了volatile 的作用以及为什么它不能保证如果两个线程同时执行i++,结果将是两个增量。它不会阻止以下情况:第一个线程读取 0,第二个线程读取 0,第一个线程递增 0 到 1,第二个线程递增 0 到 1,第一个线程写入 1,第二个线程写入 1。每个线程读取最新值,但是增量丢失是因为您需要原子性,而不是可见性。 (查看更新。)
  • 我发现“线程内存”的概念很有用。我的概念是,对值的单线程操作被优化为仅发生在寄存器中,而不是总是从源代码让您相信的“主存储器”中分配的字段读取或写入。这不正确吗?
  • @erickson 您的概念基本上是正确的,但我认为将该概念称为“线程内存”充其量是没有帮助的,最坏的情况是肯定会产生误导。此概念仅在 Java 代码级别有效,如果您想要一个仅在该级别有效的概念,“因为标准如此说”是一个更好的概念。
【解决方案2】:

使用 volatile 的用例是从映射到设备寄存器的内存读取/写入,例如在微控制器上,CPU 以外的其他东西将读取/写入值到该“内存”地址和所以编译器不应该优化那个变量。

【讨论】:

  • 感谢您的即时响应,但我仍然有些困惑...... 1.正如我之前发布的那样,我在该类中有一个数据成员 cc,我有 2 个差异线程类 Thread1 和 Thread2 所以如果我使该数据成员 cc 易失,然后如您所见,上面的输出(o / p) 2 diff 线程已产生输出:: 2Thread-0 2Thread-1 因此,如果 volatile 反映主内存中的更改而不是线程内存中的更改,那么这怎么可能?2。如果我使用同一类 Thread1 类的 2 个不同线程,或者如果我使用 2 个 diff 线程类的 2 个线程(如 Thread1 和 Thread 2)之间是否存在差异?
  • 这在 C/C++ 代码中相当常见,但我从未听说过 Java 程序中的内存映射字段。
  • @jameslarge 是的,我看过一些用于 MCU 的 Java VM(例如dmitry.gr/…),它们提供了一个更“OO”的接口来使用内存映射 IO,但我认为Java 中的用例与 C/C++ 中的用例一样有意义。
  • 当然,这是有道理的,但在大多数 JVM 实现(包括 uJ)中,对象的地址是不固定的。 GC 移动东西以防止内存碎片。您链接到的页面表明 uJ 使用本机方法(即,在平台的“本机”编程语言中实现的方法)与硬件交互,并且它附带预定义的本机方法来读取和写入任意内存地址。
【解决方案3】:

Java volatile 关键字用于将Java 变量标记为“正在存储在主内存中”。这意味着,对 volatile 变量的每次读取都将从计算机的主内存中读取,而不是从缓存中读取,并且对 volatile 变量的每次写入都将写入主内存,而不仅仅是缓存。

它保证你访问的是这个变量的最新值。

P。 S. 使用更大的循环来发现错误。例如尝试迭代 10e9 次。

【讨论】:

  • 你在说什么“CPU缓存”?
  • 如果这就是你的意思,那么你就完全(而且危险地)错了。请参阅here 以了解开始。所有具有多核的现代 CPU 和支持 Java 的此类缓存都已经具有使这些缓存一致的硬件。这是因为这些缓存对性能必不可少,并且使操作返回主内存效率低得离谱,以至于使多核实现几乎毫无意义。缓存与 each other 直接同步,使缓存不可见。
  • 顺便说一句,您需要volatile 的原因是因为Java 标准是这样说的。时期。实际上,省略volatile 主要会导致失败,因为编译器和代码生成器进行了优化。在某种程度上,也是因为在 CPU 硬件中进行了其他类似的优化。但是现代 CPU 通过主内存同步的想法是完全错误的,就像 L1/L2/L3 缓存与线程内存可见性问题有关的想法一样。
  • 所有变量“存储在主内存中”。此外,“缓存”不是 Java 概念:它是一个实现细节。此外,volatile 的语义不仅仅适用于相关变量。根据 Java 语言规范,volatile 字段的更新会与同一字段的任何后续读取建立发生在关系。这意味着在线程 B 读取volatile v; 之后,它不仅可以获取线程 A 写给v 的最新值,而且还可以看到线程 A 写给 every 变量的所有内容它在更新v之前更新。
猜你喜欢
  • 2012-02-21
  • 2011-03-31
  • 1970-01-01
  • 2015-01-30
  • 1970-01-01
  • 2019-07-20
  • 2015-03-25
  • 1970-01-01
相关资源
最近更新 更多