Java中的挥发性与静态


Answers:


365

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

当变量是易变的而不是静态的时,每个变量将只有一个Object。因此,从表面上看,它与普通变量没有什么区别,但与static完全不同。但是,即使具有Object字段,线程也可以在本地缓存变量值。

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

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


7
volatile修饰符确保任何读取字段的线程都将看到最新写入的值,因此,如果变量在多个线程之间共享并且您需要此功能,则这取决于您的用例,这是必要的。
stivlo 2012年

5
当您说“本地缓存”时,缓存是什么?CPU缓存,某种JVM缓存?
mert inan 2012年

6
@mertinan是的,该变量可以位于更靠近处理器或内核的缓存中。有关更多详细信息,请参见cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html
stivlo

15
“易失性”并不意味着“每个对象一个变量”。缺少“静态”可以做到这一点。-1表示未消除OP的基本误解。
2013年

27
@EJP我认为句子“将变量声明为volatile,每个对象将有一个变量。因此从表面上看,它与普通变量没有什么不同”,这是在解释说,我已经添加了,而不是static,随时编辑文章并改进措辞,使内容更清楚。
stivlo 2013年

288

静态和挥发性之间的区别:

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

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

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


6
您好,@ Som,如果我错了,请纠正我。但是,您不认为语句“ 而不是在Thread的上下文中,在该上下文中将一个线程更新为静态变量将立即反映所有线程(在其本地缓存中)的更改。 ”应为“但不在上下文中” ”,其中一个线程对静态变量的更新将<< NOT >>立即反映所有线程(在其本地缓存中)的更改。”
Jaikrat 2015年

@Jaikrat是的,这让我感到困惑。我的理解是,按照书面规定,您是对的,而且这个答案是错误的。如果我错了,我也想纠正。
stuart

@Jaikrat线程虽然不缓存静态变量,但是确实引用了更新的静态变量。
2015年

@Som然后您想更正该参数并删除它,但不要在Thread上下文中使用。那太令人困惑了。谢谢
Jaikrat 2015年

可悲的是,这个答案是不正确的。在现代CPU上,即使volatile变量也可以在不同的CPU缓存之间共享。这不会出现问题,因为高速缓存会在修改高速缓存行之前协商其独占所有权。
David Schwartz

32

除了其他答案外,我还想为其添加一张图片(图片易于理解)

在此处输入图片说明

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

volatile声明可确保线程不会缓存数据,而使用共享副本

图片来源


1
静态变量在线程下的对象之间共享?这应该读取静态变量在对象之间的所有对象之间共享,而与线程无关。
cquezel

1
“易失性变量在多个线程之间共享(因此对象也是如此)。” 易失性不会改变在多个线程或对象之间共享变量的方式。它更改了允许运行时缓存值的方式。
cquezel

1
您对静态变量的评论也适用于非静态变量,应该将“将被缓存”和“将不反映”改为“可能被缓存”和“可能不反映”。
cquezel

4
我很困惑。这张照片清除了我所有的问题!
Vins


4

简单来说,

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

  2. volatile:此关键字适用于变量和实例变量。

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

通过 更好地了解volatile变量来看看本文Javin Paul

在此处输入图片说明

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

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

关于您的查询:

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

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

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

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

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

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

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

尤其要看一下原子变量

相关的SE问题:

挥发性与原子性

挥发性布尔值与AtomicBoolean

Java中的volatile和Synchronized之间的区别


我真的很感谢这个答案。我知道什么是volatile较早的,但是,这个答案澄清了很多关于我为什么我仍然需要使用volatilestatic变量。
Chaklader Asfak Arefe '18

volatile:此关键字适用于类变量和实例变量。您上面说的话就适用于课堂而言是不正确的。仅适用于变量的两个关键词是易变的和瞬变的。所以挥发性将不适用于课堂。
ASR

volatile适用于类(静态)变量。在Google中检查双锁单例链接,您会发现您的理解是错误的。stackoverflow.com/questions/18093735/...
拉温德拉巴布

私有静态volatile是有效的声明。
Ravindra babu

0

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

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

如果从静态变量中获得期望的结果,然后在多线程中将volatile与static一起使用,则将解决所有问题。


0

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


-2

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

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

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


15
如果将变量声明为易失性,则所有线程将拥有其自己的变量副本,但该值是从主内存中获取的。=> 对。 因此,所有线程中变量的值将相同。=> 错误,每个线程将为相同的Object使用相同的值,但是每个Object将具有自己的副本。
stivlo 2011年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.