Java中的动态与静态

fwzugrvs  于 2023-01-15  发布在  Java
关注(0)|答案(9)|浏览(186)

static表示所有对象的值的一个副本,volatile表示所有线程的值的一个副本,这种说法正确吗?
无论如何,一个static变量值也将是所有线程的一个值,那么我们为什么要选择volatile呢?

nfzehxib

nfzehxib1#

在Java中声明一个static变量,意味着无论创建多少个类对象,都只有一个副本。即使根本没有创建Objects,变量也是可访问的。但是,线程可能已经本地缓存了它的值。
当一个变量是volatile而不是static时,每个Object都有一个变量,所以表面上看起来和普通变量没有区别,但是和static完全不同,但是即使有Object字段,线程也可能在本地缓存变量值。
这意味着,如果两个线程同时更新同一Object的变量,并且该变量未声明为volatile,则可能存在其中一个线程在缓存中具有旧值的情况。
即使你通过多个线程访问一个static值,每个线程都可以有它的本地缓存副本!为了避免这种情况,你可以声明变量为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类。

2ic8powd

2ic8powd2#

静态和易失性之间的差异:

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

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

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

hyrbngr7

hyrbngr73#

除了其他答案,我想为它添加一个图像(pic使易于理解)

static变量可以为单个线程缓存。在多线程环境中,如果一个线程修改了其缓存的数据,这可能不会反映给其他线程,因为它们有该数据的副本
volatile声明确保线程不会缓存数据,并且仅使用共享副本
image source

eoigrqb6

eoigrqb64#

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

x33g5p2x

x33g5p2x5#

简单来说,

  1. staticstatic变量与***类***关联,而不是与任何***对象***关联。该类的每个示例共享一个类变量,该变量位于内存中的一个固定位置
  2. volatile:此关键字同时适用于***类***和***示例***变量。
    使用volatile变量可以降低内存一致性错误的风险,因为对volatile变量的任何写入都会与同一变量的后续读取建立一种happens-before关系,这意味着对volatile变量的更改对其他线程始终可见
    请看article by Javin Paul,以便更好地理解volatile变量。

在没有volatile关键字的情况下,变量在每个线程堆栈中的值可能不同,通过将变量设置为volatile,所有线程在其工作内存中得到相同的值,避免了内存一致性错误。
这里的术语variable可以是static(类)变量或instance(对象)变量。
关于您的查询:
无论如何,静态变量的值对于所有线程都是一个值,那么为什么我们要选择volatile呢?
如果我的应用程序中需要instance变量,我不能使用static变量。即使是static变量,由于线程缓存,一致性也无法得到保证,如图所示。
使用volatile变量可以降低内存一致性错误的风险,因为对volatile变量的任何写入都会与同一变量的后续读取建立一个happens-before关系,这意味着对volatile变量的更改对其他线程总是可见的。
更重要的是,这也意味着当线程读取volatile变量时,它不仅看到volatile的最新变化,还看到导致变化的代码的副作用=〉***volatile变量仍然可能出现内存一致性错误***为了避免副作用,你必须使用synchronized变量,但是java中有一个更好的解决方案。

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

java.util.concurrent包中的一些类提供了不依赖于同步的原子方法。
有关详细信息,请参阅high level concurrency control文章。
特别是看一下Atomic variables
相关SE问题:
Volatile Vs Atomic
Volatile boolean vs AtomicBoolean
Java中volatile和synchronized的区别

hyrbngr7

hyrbngr76#

volatile变量值的访问将直接从主内存中进行。它应该只在多线程环境中使用。静态变量将加载一次。如果它在单线程环境中使用,即使变量的副本将被更新,也不会有任何伤害,因为只有一个线程访问它。
现在,如果在多线程环境中使用静态变量,那么如果期望从它得到所需的结果,则会出现问题。由于每个线程都有自己的副本,因此来自一个线程的静态变量的任何增量或减量可能不会反映在另一个线程中。
如果期望从静态变量得到期望的结果,那么在多线程中使用volatile和static,那么一切都将得到解决。

rjzwgtxy

rjzwgtxy7#

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

13z8s7eq

13z8s7eq8#

说static意味着所有对象的值都有一个副本是不正确的,因为static意味着每个加载包含类的类加载器只有一个副本
Java volatile关键字意味着,每次读取volatile变量时,都将从计算机的主内存中读取,而不是从CPU缓存中读取;每次写入volatile变量时,都将写入主内存,而不仅仅是CPU缓存。

8oomwypt

8oomwypt9#

如果我们声明一个变量为静态变量,那么这个变量只有一个副本,所以无论何时不同的线程访问这个变量,这个变量都只有一个最终值(因为只有一个内存位置分配给这个变量)。
如果一个变量被声明为volatile,那么所有线程都有自己的变量副本,但是变量的值是从主内存中获取的,所以所有线程中变量的值都是相同的。
因此,在这两种情况下,要点是变量的值在所有线程中都是相同的。

相关问题