关于Java里的volatile的一些见解

首先得了解一下volatile是干什么的:

volatile 关键字:当多个线程进行操作共享数据时,可以保证共享数据的内存可见性。

使用方式例如:volatile int a = 3;

什么是内存可见性?先不急着,看看下面java的内存模型的说明:

想要理解volatile为什么能确保可见性,就要先理解Java中的内存模型是什么样的。
Java内存模型规定了所有的变量都存储在主内存中。每条线程中还有自己的工作内存,线程的工作内存中
保存了被该线程所使用到的变量(这些变量是从主内存中拷贝而来)。线程对变量的所有操作(读取,赋
值)都必须在工作内存中进行。不同线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传
递均需要通过主内存来完成。



 *这时候可以解释一下可见性的含义:是指当多个线程访问同一个变量时,一个线程的工作内存修改了这个变量的值,会让主内存该变量的值立刻改变,这可保证其他线程读取共享变量的值每次都是最新的值。

还是不太理解?个人看法是,如果不加volatile修饰的普通共享变量,一个线程对其的值进行修改时,会有以下步骤:

1.在该线程内存里的变量值修改

2.再把线程内存里的值写回主内存里,更新主存内的变量值

而实际上多线程数据操作里,步骤二可能会不能及时地将线程内存修改后的值写回主存里,导致这时其他线程从主存中读取到的值是未更新的,从而发生脏读现象。

但是加上了volatile修饰后,在步骤一发生的同时,主存内的变量值也会立刻被修改,这就保证了其他线程读取共享变量的值每次都是最新的值。



volatile除了确保一个线程的修改能对其他线程是可见的。某些情况下,volatile 还能提供原子性,
例如:double 和 long 都是64位宽,因此对这两种类型的读是分为两部分的,第一次读取第一个 32 位,然后再读剩下的 32 位,这个过程不是原子的。所以,如果一个线程正在修改该 long 变量的值,这时可能该long变量对于其他变量来说只有一半是“可见的”。

因而当你知道该long、double成员变量会被多个线程访问,如计数器、价格等,你最好是将其设置为 volatile。
因为,当你对一个 volatile 变量进行写操作之前,Java 内存模型会插入一个写屏障(writebarrier),读一个volatile 变量之前,会插入一个读屏障(read barrier),这能解决上面long、double类型只能读“一半”的尴尬现象。



 *但需要注意的是:
 volatile虽然能保证数据操作的内存可见性,但其不能保证原子性

为什么呢?看下面的例子:

1.下面我们假设有两个线程对添加了volatile修饰的共享变量i进行     i++操作,就假设i的初值为3.
2.当线程一要进行i++操作时,先读取i的值3,但在还没来得及对i进行自增操作并写入主内存的时候。

线程二也读取了i的值为3,等到线程一对i进行自增操作并将volatile的可见性将更新后的值立刻写回主内存中时,主存里的i变为4。

3.可是这时已经晚了,因为线程二对i的值得读取是发生在线程一的修改前的,这是线程二里的i的值也是3,那么线程二也对i进行自增并将值写回主存中去,这时主存i的值还是4,这显然不符合我们的需求。

因而仅靠使用volatile有时候是无法保证数据的原子性的,在适当的时候我们还是需要使用synchronized或Lock等锁机制以保证共享数据操作的原子性。



这篇文章给了我很好的思路,而且写的也更详细更有意思,对volatile尚有疑惑的可以去看看:

点击打开链接


猜你喜欢

转载自blog.csdn.net/a5552157/article/details/78987658