【发布时间】:2011-01-26 06:54:40
【问题描述】:
static 表示所有对象的值的一份副本,volatile 表示所有线程的值的一份副本是否正确?
无论如何static 变量值也将是所有线程的一个值,那我们为什么要选择volatile?
【问题讨论】:
标签: java multithreading concurrency static volatile
static 表示所有对象的值的一份副本,volatile 表示所有线程的值的一份副本是否正确?
无论如何static 变量值也将是所有线程的一个值,那我们为什么要选择volatile?
【问题讨论】:
标签: java multithreading concurrency static volatile
在Java中声明一个static变量,意味着无论创建多少类对象,都只会有一个副本。即使根本没有创建Objects,也可以访问该变量。但是,线程可能有它的本地缓存值。
当变量是 volatile 而不是 static 时,每个 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可能会导致counter的最终值不为零!
要解决这个问题,你必须实现一个锁:
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 类。
【讨论】:
静态和易失的区别:
静态变量:如果两个线程(假设t1 和t2)正在访问同一个对象并更新一个声明为静态的变量,那么这意味着t1 和t2可以在各自的缓存中制作同一对象(包括静态变量)的本地副本,因此t1对其本地缓存中的静态变量所做的更新不会反映在t2缓存的静态变量中。
静态变量用于对象的上下文,其中一个对象所做的更新将反映在同一类的所有其他对象中但不在线程的上下文中其中一个线程更新到静态变量将立即将更改反映到所有线程(在它们的本地缓存中)。
易失性变量:如果两个线程(假设t1 和t2)正在访问同一个对象并更新一个声明为易失性的变量,那么这意味着t1 和t2可以制作自己的 Object 本地缓存 除了声明为 volatile 的变量 。所以 volatile 变量将只有一个主副本,将由不同的线程更新,一个线程对 volatile 变量的更新将立即反映到另一个线程。
【讨论】:
volatile 变量也可以在不同的 CPU 缓存之间共享。这没有问题,因为缓存会在修改缓存行之前协商其独占所有权。
除了其他答案,我想为它添加一张图片(图片易于理解)
static 变量可以为单个线程缓存。在多线程环境中如果一个线程修改了它的缓存数据,这可能不会反映到其他线程,因为它们有一个副本。
volatile 声明确保线程不会缓存数据并仅使用共享副本。
【讨论】:
我认为static 和volatile 根本没有关系。我建议你阅读java教程了解Atomic Access,以及为什么要使用原子访问,了解interleaved是什么,你会找到答案。
【讨论】:
简单来说,
使用 volatile 变量可降低内存一致性错误的风险,因为对 volatile 变量的任何写入都会与随后读取该相同变量建立起先发生关系。这意味着对 volatile 变量的更改始终对其他线程可见
看看Javin Paul 的这个article 以更好地理解易失性变量。
在没有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 是什么,但是,这个答案让我明白了很多,为什么我仍然需要将volatile 与static 变量一起使用。
易失性变量值访问将直接从主存储器。它应该只在多线程环境中使用。 静态变量将被加载一次。如果它在单线程环境中使用,即使变量的副本将被更新,访问它也不会因为只有一个线程而造成伤害。
现在,如果在多线程环境中使用静态变量,那么如果期望从中得到期望的结果,就会出现问题。由于每个线程都有自己的副本,因此一个线程对静态变量的任何增量或减量都可能不会反映在另一个线程中。
如果人们期望从静态变量中得到想要的结果,那么在多线程中使用 volatile 和静态,那么一切都会得到解决。
【讨论】:
不确定静态变量是否缓存在线程本地内存中。但是当我执行两个线程(T1,T2)访问同一个对象(obj)时,当 T1 线程对静态变量进行更新时,它会反映在 T2 中。
【讨论】:
如果我们将变量声明为静态变量,则该变量将只有一个副本。 因此,每当不同的线程访问该变量时,该变量将只有一个最终值(因为该变量只分配了一个内存位置)。
如果一个变量被声明为 volatile,所有线程都会有自己的变量副本,但值是从主存中获取的。所以,所有线程中的变量值都是一样的。
因此,在这两种情况下,要点是变量的值在所有线程中都是相同的。
【讨论】: