/**
* @program: mayun-quick_Netty
* @description: volatile关键字的如何保证内存可见性
* @author: Mr.Liu
* @create: 2019-11-17 12:56
**/
public class volatileTest {
public static void main(String[] args) {
ThreadDemo td = new ThreadDemo();
new Thread(td).start();
while(true){
if (td.isTag()){
System.err.println("================>>"+td.isTag());
break;
}
}
}
}
class ThreadDemo implements Runnable{
private boolean tag = false;
public boolean isTag(){
return tag;
}
public void setTag(boolean tag){
this.tag = tag;
}
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
tag = true;
System.out.println("tag = "+tag);
}
}
有两个线程,一个修改tag的值为true,主线程获取tag的值做判断,结果:
主线程未结束,当前tag的值还是false,所以没有打印========>>>true的值.
按理通过Runnable创建的线程访问的应该是共享数据,那为什么会出现这种情况?这就涉及到内存可见性。
JVM会为每个线程分配一个独立缓存提高效率
那么这个两个线程,一个是读(主线程),一个是写(线程1),我们让线程1睡了2s,说明,线程1先执行,每个线程都有一个独立的缓存,也就是说当线程1需要对主存的共享数值进行改变,它需要先把这个flag复制一份到缓存区中,
然后修改,将来再把这个值写回主存去,在写之前,主线程来了,它要读取现在在内存里面的值,现在是false,当然有一种情况,就是线程1在某个机会将flag=true写回去,
当时主线程用了while(true),这句话调用了系统底层代码,效率极高,高到主线程没有机会再次读取内存,这就是线程对共享数据操作的不可见。
内存可见性问题:当多个线程操作共享数据时,彼此不可见。
如何解决?同步锁。改写main方法
ThreadDemo td = new ThreadDemo();
new Thread(td).start();
while(true){
synchronized (td){
if (td.isTag()){
System.err.println("================>>"+td.isTag());
break;
}
}
}
但是用了锁,代表效率极低,但是我现在我不想加锁,但是有存在内存可见性的问题,我该怎么办?
关键字volatile:当多个线程进行操作共享操作时,可以保证内存中的数据可见。(内存栅栏,实时刷新)
被volatile关键字修饰的变量,在每个写操作之后,都会加入一条store内存屏障命令,此命令强制工作内存将此变量的最新值保存至主内存;在每个读操作之前,都会加入一条load内存屏障命令,此命令强制工作内存从主内存中加载此变量的最新值至工作内存。
我们可以认为它是直接在主存操作的,这个实时刷新的操作相比不加,性能略低,但是比加锁的效率显然高很多,低在哪?加了这关键字,JVM就不能进行指令重排序,无法优化代码执行。
//修改代码中这个,添加了volatile关键字
private volatile boolean tag = false;
volatile相对synchronized是一种轻量级同步策略。但是注意:
Java中long和double赋值不是原子操作,因为先写32位,再写后32位,分两步操作,这样就线程不安全了。如果改成下面的就线程安全了
private volatile long number = 8;
那么,为什么是这样?volatile关键字难道可以保证原子性?
volatile仅仅用来保证该变量对所有线程的可见性,但不保证原子性。但是我们这里的例子,volatile似乎是有时候可以代替简单的锁,似乎加了volatile关键字就省掉了锁。这不是互相矛盾吗
其实如果一个变量加了volatile关键字,就会告诉编译器和JVM的内存模型:这个变量是对所有线程共享的、可见的,每次jvm都会读取最新写入的值并使其最新值在所有CPU可见。所以说的是线程可见性,没有提原子性。
下面我们用一个例子说明volatile没有原子性,不要将volatile用在getAndOperate场合(这种场合不原子,需要再加锁,如i++),仅仅set或者get的场景是适合volatile的。
例如你让一个volatile的integer自增(i++),其实要分成3步:
这3步的jvm指令为:
mov 0xc(%r10),%r8d ; Load
inc %r8d ; Increment
mov %r8d,0xc(%r10) ; Store
lock addl $0x0,(%rsp) ; StoreLoad Barrier
注意最后一步是内存屏障。
什么是内存屏障(Memory Barrier)?
内存屏障(memory barrier)是一个CPU指令。基本上,它是这样一条指令:
编译器和CPU可以在保证输出结果一样的情况下对指令重排序,使性能得到优化。
插入一个内存屏障,相当于告诉CPU和编译器先于这个命令的必须先执行,后于这个命令的必须后执行。
内存屏障另一个作用是强制更新一次不同CPU的缓存。
上面的 中间的几步(从Load到Store)是不安全的,中间如果其他的CPU修改了值将会丢失
所以volatile保证变量对线程的可见性,但不保证原子性
参考:https://www.cnblogs.com/figsprite/p/10779904.html