繁体   English   中英

挥发性 vs Java 中的 Static

[英]Volatile vs Static in Java

static表示所有对象的值的一份副本而volatile表示所有线程的值的一份副本是否正确?

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

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

当变量是volatile而非static 时,每个Object将有一个变量。 因此,从表面上看,它似乎与普通变量没有区别,但与static完全不同。 然而,即使有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类。

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

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

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

可变变量:如果两个线程(假设t1t2 )正在访问同一个对象并更新一个声明为 volatile 的变量,那么这意味着t1t2可以创建自己的对象本地缓存,但声明为 volatile 的变量除外. 因此 volatile 变量将只有一个主副本,它将被不同的线程更新,并且一个线程对 volatile 变量所做的更新将立即反映到另一个线程。

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

在此处输入图片说明

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

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

图片来源

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

简单来说,

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

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

使用 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包中的一些类提供不依赖于同步的原子方法。

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

特别是看看Atomic variables

相关 SE 问题:

挥发性与原子

挥发性布尔值与原子布尔值

Java中volatile和synchronized的区别

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

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

如果人们期望从静态变量获得所需的结果,那么在多线程中使用 volatile 和静态,那么一切都会得到解决。

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

说 static 意味着所有对象的值的一个副本是不正确的,因为 static 意味着每个加载包含 class 的类加载器只有一个副本

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM