Java中的易失性与静态性

时间:2010-03-11 09:01:25

标签: java multithreading concurrency static volatile

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

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

8 个答案:

答案 0 :(得分:342)

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

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

这意味着如果两个线程同时更新同一个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类。

答案 1 :(得分:282)

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

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

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

易失性变量:如果两个线程(假设t2t1)正在访问同一个对象并更新一个声明为volatile的变量,那么它意味着{{1 }}和t2可以创建自己的Object 的本地缓存,但声明为volatile 的变量除外。所以volatile变量只有一个主副本,它将由不同的线程更新,一个线程对volatile变量的更新将立即反映到另一个Thread。

答案 2 :(得分:25)

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

enter image description here

可以为各个线程缓存

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

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

image source

答案 3 :(得分:5)

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

答案 4 :(得分:4)

简单来说,

  1. staticstatic个变量与 相关联,而不是与任何 对象相关联/ EM> 即可。该类的每个实例共享一个类变量,该变量位于内存中的一个固定位置

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

  3.   

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

    通过Javin Paul查看此article,以便更好地了解易变量。

    enter image description here

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

    此处术语variable可以是static(类)变量或instance(对象)变量。

    关于您的查询:

      

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

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

    使用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

答案 5 :(得分:0)

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

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

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

答案 6 :(得分:0)

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

答案 7 :(得分:-1)

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

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

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