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

Rah*_*hra 9 java multithreading synchronization volatile thread-safety

我试图理解多线程如何在Java中工作.我明白之间的差别VolatileSynchronization.

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

片段Thinking In Java-

同步还会导致刷新到主内存,因此如果某个字段完全被同步的方法或块保护,则不必使其变为易失性.

如果类只有一个可变字段,那么通常只使用volatile而不是synchronized来安全.同样,您的第一选择应该是使用synchronized关键字 - 这是最安全的方法,并且尝试做任何其他事情都是有风险的.

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

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

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

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

ass*_*ias 12

简化一点:

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

所以要回答你的问题,不,如果一个变量被写入一个synchronized块中,你就不需要标记它volatile,前提是你总是synchronized使用同一个监视器从一个块中读取该变量.


以下是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
  }
}
Run Code Online (Sandbox Code Playgroud)

以下是一些例子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
    }
  }
}
Run Code Online (Sandbox Code Playgroud)


Rea*_*tic 6

JLS在程序中的指令上定义了一个名为"before-before"的关系.一个简短的版本可以在文档中java.util.concurrent看到.

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

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

因此,如果线程A写入x 同步块内的变量,并且线程B从x 同一监视器上的同步块内部读取,则x不需要是volatile - 写入"发生在"读取之前,其结果将是可见的线程B.

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

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