【问题标题】:Volatile keyword in Java - Clarification [duplicate]Java中的volatile关键字-澄清[重复]
【发布时间】:2011-04-05 21:55:09
【问题描述】:

我真的很困惑我读到的关于 volatile 关键字在 java 中的应用。

  1. 以下说法正确吗? “对 volatile 字段的写入发生在对同一字段的每次后续读取之前”

  2. 理想情况下应该在什么时候使用 volatile 关键字?

  3. 有什么区别:

    class TestClass
    {  private int x;
    
       synchronized int get(){return x;}
       synchronized void set(int x){this.x = x;}
    
    }
    

class TestClass
{  private volatile int x;

   int get(){return x;}
   void set(int x){this.x = x;}

}

【问题讨论】:

  • 澄清我的自动“可能”重复评论:前两个建议不是完全重复的;我的是。重要的是,将此问题作为重复解决,因为该问题的选定答案在说明 volatile 仅影响声明为 volatile 的字段时是危险的不正确 - 事实上,对任何 volatile 变量的写入和读取都会引入完整的内存屏障,就像两个线程之间的同步一样。

标签: java volatile


【解决方案1】:

volatile字段修饰符,而synchronized修饰代码块方法。因此,我们可以使用这两个关键字指定简单访问器的三种变体:

     int i1;
     int geti1() {return i1;}

     volatile int i2;
     int geti2() {return i2;}

     int i3;
     synchronized int geti3() {return i3;}

geti1() 访问当前线程中i1 中当前存储的值。 线程可以有变量的本地副本,并且数据不必与其他线程中保存的数据相同。特别是,另一个线程可能在其线程中更新了i1,但当前线程中的值可能是与更新后的值不同。事实上,Java 有“主”内存的概念,这是保存变量当前“正确”值的内存。线程可以拥有自己的变量数据副本,并且线程副本可以不同于“主”内存。所以实际上,对于i1,“主”内存的值可能为1,对于i1,线程1 的值可能为2如果 thread1thread2 都更新了 i1 但这些更新的值尚未传播到“主”内存或其他线程。

另一方面,geti2() 有效地从“主”内存访问i2 的值。不允许 volatile 变量具有与“主”内存中当前保存的值不同的变量的本地副本。实际上,声明为 volatile 的变量必须在所有线程中同步其数据,这样每当您在任何线程中访问或更新变量时,所有其他线程都会立即看到相同的值。通常 volatile 变量比“普通”变量具有更高的访问和更新开销。通常允许线程拥有自己的数据副本是为了提高效率。

volitile 和 synchronized 有两个区别。

首先同步获取并释放监视器上的锁,该锁一次只能强制一个线程执行代码块。这是同步的众所周知的方面。但是 synchronized 也会同步内存。实际上 synchronized 将整个线程内存与“主”内存同步。所以执行geti3() 会执行以下操作:

  1. 线程获取对象 this 的监视器上的锁。
  2. 线程内存刷新其所有变量,即它的所有变量都有效地从“主”内存中读取。
  3. 代码块被执行(在这种情况下,将返回值设置为 i3 的当前值,它可能刚刚从“主”内存中重置)。
  4. (对变量的任何更改现在通常都会写入“主”内存,但对于 geti3(),我们没有任何更改。)
  5. 线程释放对象 this 的监视器上的锁。

所以其中 volatile 只在线程内存和“主”内存之间同步一个变量的值,synchronized 在线程内存和“主”内存之间同步所有变量的值,并锁定和释放一个监视器以启动。显然,同步的开销可能比 volatile 的开销更大。

http://javaexp.blogspot.com/2007/12/difference-between-volatile-and.html

【讨论】:

  • 这里关于同步只有一个变量的说法是不正确的,而且通常所有写的东西听起来都不是绝对正确的。
  • 这里关于同步只有一个变量的说法是不正确的,而且通常所写的所有内容听起来都不是绝对正确的。写入 volatile 与监视器释放具有相同的记忆效应。从 volatile 读取具有与监视器获取操作相同的记忆效应。这意味着对 volatile 变量的访问也会影响其他变量。此外,没有像“Volatile Flow”中提到的变量上的块,有写入内存和缓存失效。
  • 好的链接在这里 - cs.umd.edu/~pugh/java/memoryModel 和 -1 用于基础知识的混乱:)
【解决方案2】:

volatile 保证从变量读取始终反映最新的更新值。运行时可以通过多种方式实现这一点,包括不缓存或在值更改时刷新缓存。

【讨论】:

    【解决方案3】:

    bwawok 避开了它,但 volatile 关键字不仅仅用于内存可见性。在 Java 1.5 发布之前,volatile 关键字声明该字段将通过每次读取主内存和写入刷新来获取对象的最新值。

    今天的 volatile 关键字 syas 有两件非常重要的事情:

    1. 不用担心如何,但要知道,在读取 volatile 字段时,您将始终拥有最新的值。
    2. 编译器无法重新排序易失性读/写以维护程序顺序。

    【讨论】:

      【解决方案4】:

      从客户端的角度来看,私有 volatile 字段对公共接口隐藏,而同步方法更可见。

      【讨论】:

      • 同步不是公共接口或方法签名的一部分。
      • 我的立场是正确的。这实际上是一个实现细节。
      【解决方案5】:

      回答您问题的第 3 部分,部分回答第 2 部分。

      synchronizedvolatile 样本之间没有功能区别。

      但是,就性能而言,每种方法都有其自身的缺点。在某些情况下,volatile 的性能可能比仅使用synchronized 或来自java.util.concurrent 的其他原语更差。有关此问题的讨论,请参阅 -> Why aren't variables in Java volatile by default?

      【讨论】:

        【解决方案6】:

        Kerem Baydoğan 的回答完全正确。我只是想举一个实际的例子来说明volatile 关键字为我们提供了什么。

        首先,我们有一个计数器,smth like

        public class Counter {
            private int x;
            public int getX() { return x; }
            public void increment() { x++; }
        }
        

        还有一些增加 x 值的 Runnable 任务

        @Override
        public void run() {
            for (N) {
                    int oldValue = counter.getX();
                    counter.increment();
                    int new value = counter.getX();
                }
        
            }
        }
        

        如果没有同步,interference between threads 将无法正常工作

        解决这个问题的最简单方法:

        public class Counter {
            private int x;
            public synchronized int getX() { return x; }
            public synchronized void increment() { x++; }
        }
        

        其实为了强制系统崩溃,我在读写x之前做了一个Thread.sleep,试想是BD还是要处理的大任务。


        现在,volatile 有什么用处?那里有很多好文章:volatile articlethis question

        同步对公共资源的访问不是答案,但持有标志以停止线程

        是一个不错的选择

        我是我们的上一个。例如,假设我们想将变量增加到 100,一个简单的方法可能是一个 volatile boolean 标志。示例:

        private volatile boolean stop;
        
        @Override
        public void run() {
            while(!stop) {
                int oldValue = counter.getX();
                if (oldValue == 100) {
                     stop = true;
                } else {
                     counter.increment();
                     int new value = counter.getX();
                }       
             }
        }
        

        这可以正常工作,但是,如果您从标志中删除 volatile 关键字,则可能会遇到无限循环。

        【讨论】: