Java线程:所有共享变量都应该是易失性的吗?

时间:2017-07-27 13:10:13

标签: java multithreading synchronization thread-safety volatile

我试图了解多线程如何在Java中工作。我理解VolatileSynchronization之间的区别。

Volatile是关于可见性的,并不保证同步。当我们使用多线程环境时,每个线程在它们正在处理的变量的本地缓存上创建自己的副本。更新此值时,更新首先在本地缓存副本中发生,而不是在实变量中发生。因此,其他线程与其他线程正在更改的值无关。这就是volatile出现的地方。易失性字段立即写入主存储器,并从主存储器读取。

来自Thinking In Java -

的摘录
  

同步也会导致刷新到主内存,所以如果是一个字段   完全由同步方法或块保护,它不是   使它变得不稳定所必需的。

     

通常只有安全使用volatile而不是synchronized if   该类只有一个可变字段。再次,你的第一选择应该   是使用synchronized关键字 - 这是最安全的方法,和   试图做任何其他事情都有风险。

但我的问题是,如果在同步块中,正在修改非易失性共享变量,其他线程是否会看到更新的数据? (由于所讨论的变量是非易失性的,因此其他线程应该从缓存而不是主要主存储器中读取陈旧数据)

如果对上述问题的回答是NO,那么我可以得出结论,每当我使用同步时,我应该确保必须将共享变量标记为volatile吗?

如果答案是YES,那么这是否意味着我总是可以使用synchronization而不是将共享变量标记为volatile

p.s:在提出这个问题之前,我已经在StackOverflow和其他网站上阅读了很多答案,但我无法找到问题的答案。

3 个答案:

答案 0 :(得分:12)

简化一点:

  • volatile仅提供可见性:当您读取volatile变量时,您会得到两个保证:(1)您看到对变量的最新写入,即使它是在另一个线程中执行的(2) )volatile写之前的所有写操作也都可见。
  • synchronized为您提供可见性和原子性 - 线程观察使用同一监视器从synchronized块中synchronized块中执行的操作将查看所有这些操作或不查看它们。

所以要回答你的问题,不,如果变量写在synchronized块内,你不需要将它标记为volatile,前提是你总是从synchronized读取该变量{1}}阻止使用相同的监视器。

以下是一些使用volatile的例子:

static class TestVolatile {
  private int i = 0;
  private volatile int v = 0;

  void write() {
    i = 5;
    v = 7;
  }

  void read() {
    //assuming write was called beforehand
    print(i); //could be 0 or 5
    print(v); //must be 7
    print(i); //must be 5
  }

  void increment() {
    i = i + 1; //if two threads call the method concurrently
               //i could be incremented by 1 only, not 2: no atomicity
  }
}

以及synchronized的一些示例:

static class TestSynchronized {
  private int i = 0;
  private int j = 0;

  void write() {
    synchronized(this) {
      i = 5;
      j = 7;
    }
  }

  void read_OK() {
    synchronized(this) {
      //assuming write was called beforehand
      print(i); //must be 5
      print(j); //must be 7
      print(i); //must be 5
    }
  }

  void read_NOT_OK() {
    synchronized(new Object()) { //not the same monitor
      //assuming write was called beforehand
      print(i); //can be 0 or 5
      print(j); //can be 0 or 7
    }        
  }

  void increment() {
    synchronized(this) {
      i = i + 1; //atomicity guarantees that if two threads call the method
                 //concurrently, i will be incremented twice
    }
  }
}

答案 1 :(得分:6)

JLS在程序中的指令中定义了一个名为“before-before”的关系。可以看到简短版本in the documentation of java.util.concurrent

如果写入“发生在读取之前”,则对同一变量的读操作可以看到对变量的写操作。

现在,如果两个线程仅在同步块内部访问该变量,则从同步块退出可以保证在同一个同步监视器的下一次锁定之后发生的任何事情之前“发生 - 之前发生的事情。” p>

因此,如果线程A写入同步块中的变量x ,并且线程B从同一监视器上同步块内的x 读取< / em>,然后x不需要是volatile - 写入“发生在”读取之前,其结果将对线程B可见。

但是如果线程B在没有同步的情况下读取变量,那么即使线程A在同步中执行它,也不能保证写入“在之前发生”,并且变量是不安全的 - 除非它是volatile。 / p>

因此,如果您确保所有访问 - 包括读取和写入 - 都在同一监视器上的同步块内,那么您可以依赖“之前发生”关系来使您的写入可见。

答案 2 :(得分:0)

如果该变量在每次访问时都受到同一个监视器锁的保护,那么就不需要使其变得易变。

同步块执行两项操作:单点线程访问受锁定保护的区域和可见性效果。可见性效果意味着在受到该锁定保护的任何其他线程进入使用它的区域(锁定)时,对该变量进行的任何更改都将是可见的。