Java中的Volatile与Static区别

287

static意味着一个值为所有对象提供一个副本,而volatile意味着一个值为所有线程提供一个副本,这种说法正确吗?

不管怎样,static变量的值也会为所有线程提供一个值,那么为什么我们要使用volatile


volatile 的官方解释:http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#volatile - Vadzim
9个回答

397
在Java中声明一个静态变量意味着无论类的对象创建了多少个,都只会有一个副本。即使没有创建任何对象,该��量也是可访问的。但是,线程可能具有局部缓存值。

当一个变量是volatile而不是static时,每个对象都会有一个变量。因此,表面上似乎与普通变量没有区别,但与static完全不同。然而,即使是Object字段,线程也可能在本地缓存变量值。

这意味着如果两个线程同时更新同一对象的变量,并且该变量没有被声明为volatile,那么就可能存在一种情况,其中一个线程在缓存中具有旧值。

即使通过多个线程访问static值,每个线程也可以拥有自己的本地缓存副本!为了避免这种情况,可以将变量声明为static 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类。


10
“volatile”关键字确保任何读取该字段的线程都将看到最新写入的值,因此如果变量在多个线程之间共享并且您需要此功能,则需要使用它,具体取决于您的用例。 - stivlo
5
当你说“本地缓存”时,缓存是指什么?CPU缓存,某种JVM缓存吗? - mert inan
6
是的,该变量可以存在更靠近处理器或核心的缓存中。详见http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html了解更多详情。 - stivlo
21
"volatile"并不意味着"每个对象一个变量"。缺少"static"才会这样。因为没有澄清问题发帖者基本误解的情况,所以被扣了1分。 - user207421
28
@EJP 我认为这句话“将变量声明为volatile,每个对象都会有一个变量。因此,表面上似乎与普通变量没有区别”是在解释这一点。我加了 而不是静态的 ,请随意编辑文章并改进措辞,使其更清晰明了。 - stivlo
显示剩余8条评论

297

静态变量和易失性变量的区别 :

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

静态变量用于对象上下文,其中一个对象的更新将反映在同一类的所有其他对象中,但在线程上下文中不会如此。其中一个线程对静态变量的更新将立即反映在所有线程的本地缓存中。

易失性变量: 如果两个线程(假设是t1t2)正在访问同一个对象并更新一个被声明为易失性的变量,则意味着t1t2可以在各自的缓存中创建对象的本地副本,但是被声明为易失性的变量将只有一个主要副本,不会被缓存。因此,不同线程对易失性变量所做的更新将立即反映到另一个线程。


7
你好@Som,请纠正我如果我错了。但是你不认为这句话:“但在线程的上下文中,一个线程对静态变量的更新将立即反映在所有线程(在它们的本地缓存中)中,”应该改为“但在线程的上下文中,一个线程对静态变量的更新将**<<不会>>**立即反映在所有线程(在它们的本地缓存中)中。” - Jaikrat
@Jaikrat 是的,那对我来说非常令人困惑。我的理解是你是正确的,而这个答案是错误的,就照它所写的来看。如果我错了,也希望能得到纠正。 - stuart
@Jaikrat 线程不会缓存静态变量,但是会引用更新后的静态变量。 - Som
@Som,那你是否想更正这段话并删除“但不在Thread的上下文中”这句话?那很令人困惑。谢谢。 - Jaikrat
很遗憾,这个答案是不正确的。在现代CPU上,即使是volatile变量也可以在不同的CPU缓存之间共享。这并不会造成问题,因为缓存在修改缓存行之前会协商独占所有权。 - David Schwartz
你好 @Som,线程能否像普通原始类型一样缓存AtomicInteger变量? - Peter

49

除了其他答案,我想添加一张图以便理解:

enter image description here

static 变量可能会被缓存到每个线程中。在多线程环境下,如果一个线程修改了它的缓存数据,那么其他线程可能无法反映出这种变化,因为它们只有一份副本

volatile 声明确保线程仅使用 共享的副本而不是缓存数据

图片来源


1
静态变量在线程下是共享的吗?应该改为静态变量在所有对象之间共享,无论线程如何。 - cquezel
1
"易失变量在多个线程(因此也包括对象)之间共享。但是,易失性并不会改变变量在多个线程或对象之间共享的方式,它只会改变运行时缓存值的方式。" - cquezel
1
你关于静态变量的评论同样适用于非静态变量,"将被缓存"和"不会反映"应该重新表述为"可能被缓存"和"可能不反映"。 - cquezel
6
我非常困惑,但这张图片解答了我所有的问题! - vins
完美的解释! - Eugene Maysyuk

7
我认为staticvolatile没有任何关系。我建议您阅读Java教程以了解原子访问及其使用原因,理解什么是交错,您将找到答案。

5

简单来说,

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

  2. volatile:这个关键字适用于实例变量。

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

请查看Javin Paul文章,以更好地理解 volatile 变量。

enter image description here

在缺少volatile关键字的情况下,每个线程堆栈中变量的值可能不同。通过将变量设置为volatile,所有线程都将获得相同的工作内存中的值,并且避免了内存一致性错误。
这里的术语variable可以是静态(类)变量或实例(对象)变量。
关于您的查询:
无论如何,静态变量的值也将成为所有线程的一个值,那么为什么我们要使用volatile呢?
如果我需要在我的应用程序中使用instance变量,我不能使用静态变量。即使在静态变量的情况下,由于线程缓存,一致性也无法保证,如图所示。
使用volatile变量可以降低内存一致性错误的风险,因为对volatile变量的任何写操作都会与该变量的后续读取建立happens-before关系。这意味着对volatile变量的更改始终对其他线程可见。
此外,这也意味着当一个线程读取一个volatile变量时,它不仅可以看到volatile的最新更改,还可以看到导致更改的代码的副作用 => 使用volatile变量仍然可能存在内存一致性错误。为了避免副作用,您必须使用synchronized变量。但是在Java中有更好的解决方案。
使用简单的原子变量访问比通过同步代码访问这些变量更有效率。 java.util.concurrent包中的某些类提供了不依赖于同步的原子方法。
有关详细信息,请参阅高级并发控制文章。
特别注意原子变量
相关SE问题: Volatile Vs Atomic

易失性布尔类型 vs 原子布尔类型

Java中volatile和synchronized的区别


我非常感谢这个答案。我之前知道什么是volatile,但是这个答案为我澄清了很多问题,为什么我仍然需要在static变量中使用volatile - Arefe
volatile:这个关键字适用于类和实例变量。你之前说的那句话是错误的,关键字只适用于变量的有两个,即volatile和transient。所以volatile不适用于类。 - ASR
volatile 关键字适用于类(静态)变量。在 Google 上查找 Double locked singleton 链接,您会发现您的理解是错误的。https://dev59.com/xmMl5IYBdhLWcg3w1py6 - Ravindra babu
私有的静态易变量是有效的声明。 - Ravindra babu

2

错误的说法是静态意味着所有对象只有一个值的副本,因为静态意味着每个加载包含类的类加载器只有一个副本

Java中的volatile关键字意味着每次读取volatile变量都将从计算机的主内存中读取,而不是从CPU缓存中读取,并且每次写入volatile变量都将写入主内存,而不仅仅是写入CPU缓存。


0

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


-1

volatile变量的值访问将直接从主内存中进行。它应该仅在多线程环境中使用。

静态变量将被加载一次。如果它在单线程环境中使用,即使变量的副本被更新,访问它也不会有任何影响,因为只有一个线程。

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

如果希望从静态变量中获得所需的结果,则在多线程中使用volatile和static,然后一切都将得到解决。


-2
如果我们将变量声明为静态的,那么该变量只有一个副本。因此,每当不同的线程访问该变量时,该变量将只有一个最终值(因为为该变量分配了一个内存位置)。
如果将变量声明为易失性的,则所有线程都将拥有自己的变量副本,但是该值取自主内存。因此,所有线程中的变量值将相同。
因此,在这两种情况下,主要问题是变量的值在所有线程中都相同。

15
如果一个变量被声明为volatile,所有线程将拥有自己的变量副本,但是该值取自主内存。正确。因此,在所有线程中,变量的值将相同。错误,每个线程将为相同的对象使用相同的值,但每个对象将有自己的副本。 - stivlo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接