Java中的易失性与静态性

问题描述 投票:251回答:8

是否正确地说static表示所有对象的值的一个副本,而volatile表示所有线程的值的一个副本?

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

java multithreading concurrency static volatile
8个回答
346
投票

在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;
}

同时执行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;
  }
}

或者使用AtomicInteger类。


282
投票

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

静态变量:如果两个线程(假设t1t2)正在访问同一个对象并更新一个声明为static的变量,则表示t1t2可以在各自的对象中创建自己的同一对象的本地副本(包括静态变量)缓存,因此t1对其本地缓存中的静态变量所做的更新不会反映在t2缓存的静态变量中。

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

易失性变量:如果两个线程(假设t1t2)正在访问同一个对象并更新一个声明为volatile的变量,则意味着t1t2可以创建自己的Object本地缓存,除了声明为volatile的变量。因此,volatile变量只有一个主副本,它将由不同的线程更新,并且由一个线程对volatile变量进行的更新将立即反映到另一个Thread。


25
投票

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

enter image description here

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

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

image source


5
投票

我认为staticvolatile完全没有任何关系。我建议你阅读java教程,了解Atomic Access,为什么使用原子访问,了解什么是interleaved,你会找到答案。


4
投票

简单来说,

  1. staticstatic变量与类相关联,而不是与任何对象相关联。该类的每个实例共享一个类变量,该变量位于内存中的一个固定位置
  2. volatile:此关键字适用于类和实例变量。

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

通过article查看这个Javin Paul,以更好的方式理解volatile变量。

enter image description here

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

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

关于你的查询:

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

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

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

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

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

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

有关更多详细信息,请参阅此high level concurrency control文章。

特别是看看Atomic variables

相关的SE问题:

Volatile Vs Atomic

Volatile boolean vs AtomicBoolean

Difference between volatile and synchronized in Java


0
投票

volatile变量值访问将直接来自主内存。它应该只在多线程环境中使用。静态变量将被加载一次。如果它在单线程环境中使用,即使变量的副本将被更新,并且访问它也不会有害,因为只有一个线程。

现在,如果在多线程环境中使用静态变量,那么如果期望从中获得期望的结果,则会出现问题。由于每个线程都有自己的副本,因此来自一个线程的静态变量的任何增量或减量可能不会反映在另一个线程中。

如果我们期望从静态变量得到所需的结果,那么在多线程中使用volatile和static,那么一切都将得到解决。


0
投票

不确定静态变量是否缓存在线程本地内存中或不是。但是当我执行两个线程(T1,T2)访问同一个对象(obj)时,当T1线程更新为静态变量时,它反映在T2中。


-1
投票

如果我们将变量声明为static,则只有一个变量副本。因此,每当不同的线程访问该变量时,变量只有一个最终值(因为只有一个内存位置为变量分配)。

如果变量被声明为volatile,则所有线程都将拥有自己的变量副本,但该值是从主内存中获取的。因此,所有线程中变量的值都是相同的。

因此,在这两种情况下,重点是变量的值在所有线程中都是相同的。

© www.soinside.com 2019 - 2024. All rights reserved.