Java中的易失性与静态性

Jot*_*thi 260 java concurrency static multithreading volatile

说这static意味着所有对象的值的volatile一个副本并且意味着所有线程的值的一个副本是否正确?

无论如何,static变量值也将成为所有线程的一个值,那么我们为什么要这样做volatile呢?

sti*_*vlo 358

在Java中声明一个静态变量意味着无论创建了多少个类的对象,都只有一个副本.即使没有Objects创建,也可以访问该变量.但是,线程可能具有本地缓存​​的值.

当变量是易失性而非静态变量时,每个变量都会有一个变量Object.因此,从表面上看,似乎与正常变量没有区别,但与静态变量完全不同.但是,即使使用Object字段,线程也可以在本地缓存变量值.

这意味着如果两个线程同时更新同一个Object的变量,并且该变量未声明为volatile,则可能存在其中一个线程在缓存中具有旧值的情况.

即使您通过多个线程访问静态值,每个线程也可以拥有其本地缓存副本!为了避免这种情况,您可以将变量声明为静态volatile,这将强制线程每次读取全局值.

但是,volatile不能代替正确的同步!
例如:

private static volatile int counter = 0;

private void concurrentMethodWrong() {
  counter = counter + 5;
  //do something
  counter = counter - 5;
}
Run Code Online (Sandbox Code Playgroud)

concurrentMethodWrong同时执行多次可能导致计数器的最终值不等于零!
要解决此问题,您必须实现锁定:

private static final Object counterLock = new Object();

private static volatile int counter = 0;

private void concurrentMethodRight() {
  synchronized (counterLock) {
    counter = counter + 5;
  }
  //do something
  synchronized (counterLock) {
    counter = counter - 5;
  }
}
Run Code Online (Sandbox Code Playgroud)

或者使用AtomicInteger课程.

  • @EJP我认为句子"声明一个变量为volatile,每个Object都会有一个变量.所以表面看起来与正常变量没什么区别"解释说,我添加了*而不是静态*,随意编辑文章并改进措辞,使其更清晰. (27认同)
  • 'volatile'确实***并不意味着'每个对象一个变量'.缺乏"静态"就是这样.-1因为未能清除OP的这种基本误解. (15认同)
  • volatile修饰符保证读取字段的任何线程都会看到最近写入的值,因此如果变量在多个线程之间共享并且您需要此功能,则需要使用此功能,这取决于您的用例. (7认同)
  • @mertinan是的,变量可以位于更接近处理器或核心的缓存中.有关详细信息,请参阅http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html. (6认同)
  • 当您说"本地缓存"时缓存是多少?CPU缓存,某种JVM缓存? (5认同)
  • 如果只有一个线程写入volatile变量,则@ernesto volatile就足够了.如果两个或多个线程可以写入,那么您也需要同步. (4认同)
  • @filip counter = counter + 5不是原子操作.线程可以在其中间切换,即在读取计数器之后但在添加5之前. (3认同)

Som*_*Som 284

静态和易失性之间的区别:

静态变量:如果两个线程(假设t1t2)访问相同的对象,并更新其声明为静态的话,那就意味着一个变量t1,并t2可以在其各自的缓存使得同一对象(包括静态变量)的自己的本地副本,所以更新通过提出t1在其本地缓存中的静态变量不会反映在静态变量t2缓存.

静态变量在Object上下文中使用,其中由一个对象进行的更新将反映在同一个类的所有其他对象中但不在Thread的上下文中,其中一个线程更新为静态变量将立即将更改反映到所有线程(在其本地缓存中).

volatile变量:如果两个线程(假设t1t2)访问相同的对象,并更新其声明为挥发性那么就意味着一个变量t1,并t2可以使对象的自己的本地缓存,除了被声明为易失性变量.因此,volatile变量只有一个主副本,它将由不同的线程更新,并且由一个线程对volatile变量进行的更新将立即反映到另一个Thread.

  • 你好@Som,如果我错了请纠正我.但是你不认为语句"**但不在Thread的上下文中,一个线程更新到静态变量将立即反映所有线程(在其本地缓存中)的变化.**"应该是"但是不在Thread的上下文中,一个线程更新为静态变量**<< NOT >>**会立即反映所有线程(在其本地缓存中)的变化." (6认同)

mrs*_*vas 28

除了其他答案,我想为它添加一个图像(图片很容易理解)

在此输入图像描述

static可以为各个线程缓存变量.在多线程环境中,如果一个线程修改了它的缓存数据,那么它可能无法反映其他线程,因为它们有一个副本.

volatile声明确保线程不会缓存数据并使用共享副本.

图像源

  • 我很困惑.这张照片清除了我的所有问题! (4认同)

Ami*_*bha 5

我认为staticvolatile没有任何关系.我建议你阅读java教程,了解原子访问,以及为什么使用原子访问,了解什么是交错,你会找到答案.


Rav*_*abu 5

简单来说,

  1. static:变量与static关联,而不是与任何对象关联。类的每个实例共享一个类变量,该变量位于内存中的一个固定位置

  2. volatile:该关键字适用于类变量实例变量。

使用易失性变量可以降低内存一致性错误的风险,因为对易失性变量的任何写入都会与同一变量的后续读取建立先发生关系。这意味着对 volatile 变量的更改始终对其他线程可见

看看这篇文章,以Javin Paul 更好地理解易失性变量。

在此输入图像描述

如果没有volatile关键字,每个线程堆栈中的变量值可能不同。通过将变量设置为volatile,所有线程将在其工作内存中获得相同的值,并且避免了内存一致性错误。

这里的术语variable可以是static(类)变量或instance(对象)变量。

关于您的查询:

无论如何,静态变量值也将是所有线程的一个值,那么为什么我们应该选择 volatile 呢?

如果instance我的应用程序需要变量,我就不能使用static变量。即使在变量的情况下static,由于线程缓存的原因,也不能保证一致性,如图所示。

使用volatile变量可以降低内存一致性错误的风险,因为对易失性变量的任何写入都会与同一变量的后续读取建立先发生关系。这意味着对易失性变量的更改始终对其他线程可见。

更重要的是,这也意味着当一个线程读取一个 volatile 变量时,它不仅看到了 volatile 的最新变化,而且还看到了导致变化的代码的副作用 => 易失性变量仍然可能出现内存一致性错误。为了避免副作用,必须使用同步变量。但java中有更好的解决方案。

使用简单的原子变量访问比通过同步代码访问这些变量更有效

包中的一些类java.util.concurrent提供不依赖于同步的原子方法。

有关更多详细信息,请参阅这篇高级并发控制文章。

特别是看看原子变量

相关SE问题:

易失性与原子性

易失性布尔值与原子布尔值

Java中易失性和同步的区别