是否正确地说这static
意味着所有对象的值的volatile
一份副本并且意味着所有线程的值的一份副本?
无论如何,一个static
变量值也将是所有线程的一个值,那么我们为什么要这样做volatile
呢?
是否正确地说这static
意味着所有对象的值的volatile
一份副本并且意味着所有线程的值的一份副本?
无论如何,一个static
变量值也将是所有线程的一个值,那么我们为什么要这样做volatile
呢?
在 Java 中声明一个静态变量,意味着无论创建多少类对象,都只会有一个副本。Objects
即使根本没有创建变量,也可以访问该变量。但是,线程可能具有它的本地缓存值。
当一个变量是volatile而非static时,每个变量都会有一个变量Object
。因此,从表面上看,它似乎与普通变量没有区别,但与static完全不同。但是,即使有Object
字段,线程也可以在本地缓存变量值。
这意味着如果两个线程同时更新同一个对象的变量,并且该变量未声明为 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
。
静态变量在Object 的上下文中使用,其中一个对象所做的更新将反映在同一类的所有其他对象中,但不在 Thread 的上下文中,其中一个线程对静态变量的更新将立即反映对所有其他对象的更改线程(在它们的本地缓存中)。
易失性变量:如果两个线程(假设t1
和t2
)正在访问同一个对象并更新一个声明为 volatile 的变量,那么这意味着t1
并且t2
可以创建自己的对象本地缓存,但声明为 volatile 的变量除外。因此 volatile 变量将只有一个主副本,该主副本将由不同的线程更新,并且一个线程对 volatile 变量的更新将立即反映到另一个线程。
除了其他答案,我想为其添加一张图片(图片易于理解)
static
变量可以为单个线程缓存。在多线程环境中,如果一个线程修改其缓存数据,则可能不会反映其他线程,因为它们拥有它的副本。
volatile
声明确保线程不会缓存数据并仅使用共享副本。
我认为static
和volatile
根本没有关系。我建议您阅读 java 教程以了解Atomic Access,以及为什么要使用 atomic access,了解什么是interleaved,您会找到答案。
简单来说,
使用 volatile 变量可以降低内存一致性错误的风险,因为对 volatile 变量的任何写入都会与后续读取该相同变量建立起先发生关系。这意味着对 volatile 变量的更改始终对其他线程可见
看看这篇文章,Javin Paul
以更好地理解 volatile 变量。
在没有volatile
关键字的情况下,每个线程堆栈中的变量值可能不同。通过将变量设为volatile
,所有线程将在其工作内存中获得相同的值,并且避免了内存一致性错误。
这里的术语variable
可以是static
(类)变量或instance
(对象)变量。
关于您的查询:
无论如何,一个静态变量值也将是所有线程的一个值,那么我们为什么要选择 volatile 呢?
如果我instance
的应用程序需要变量,我不能使用static
变量。即使在static
变量的情况下,由于线程缓存,也不能保证一致性,如图所示。
使用volatile
变量可以降低内存一致性错误的风险,因为对 volatile 变量的任何写入都会与后续读取该相同变量建立起之前发生的关系。这意味着对 volatile 变量的更改始终对其他线程可见。
更重要的是,这也意味着当一个线程读取一个 volatile 变量时,它不仅会看到 volatile 的最新更改,还会看到导致更改的代码的副作用 =>使用 volatile 变量仍然可能出现内存一致性错误. 为避免副作用,您必须使用同步变量。但是在java中有更好的解决方案。
使用简单的原子变量访问比通过同步代码访问这些变量更有效
包中的一些类java.util.concurrent
提供了不依赖于同步的原子方法。
有关更多详细信息,请参阅此高级并发控制文章。
特别是看看原子变量。
相关的 SE 问题:
volatile 变量值访问将直接从主存储器进行。它应该只在多线程环境中使用。静态变量将被加载一次。如果它在单线程环境中使用,即使变量的副本将被更新并且访问它也不会受到伤害,因为只有一个线程。
现在,如果在多线程环境中使用静态变量,那么如果期望从中得到期望的结果,就会出现问题。由于每个线程都有自己的副本,因此来自一个线程的静态变量的任何增量或减量可能不会反映在另一个线程中。
如果人们期望从静态变量获得所需的结果,那么在多线程中使用 volatile 和静态,那么一切都会得到解决。
不确定静态变量是否缓存在线程本地内存中。但是当我执行两个线程(T1,T2)访问同一个对象(obj)时,当 T1 线程对静态变量进行更新时,它会反映在 T2 中。
如果我们将变量声明为静态变量,则该变量将只有一个副本。因此,每当不同的线程访问该变量时,该变量将只有一个最终值(因为该变量只分配了一个内存位置)。
如果一个变量被声明为 volatile,所有线程都将拥有自己的变量副本,但该值是从主内存中获取的。因此,所有线程中的变量值将是相同的。
因此,在这两种情况下,要点是变量的值在所有线程中都是相同的。