volatile 底层实现原理

一、简介

        volatile 适用于一个线程写,一个线程读的场景。

        volatile 的底层实现原理是内存屏障,即 Memory Barrier。针对于 volatile 修饰的变量,当接收到写指令(修改变量值)后会加入写屏障,当接收到读指令(获取变量值)前会加入读屏障。

二、如何保证可见性

        写屏障保证在该屏障之前的,对共享变量的修改,都同步到主存当中,如下代码中,ready 是使用 volatile 修饰的。此时就会把 num ready 的最新值都同步到主存中。

public void actor2(I_Result r) {
	num = 2;
	ready = true; // ready 是 volatile 赋值带写屏障
	// 写屏障
}

        而读屏障保证在该屏障之后,对共享变量的读取,加载的是主存中的最新数据,如下代码,ready 是使用 volatile 修饰的,当获取 ready 值的时候就会在其之前加入读屏障,保证读取的 ready 的值是从主从中读取的。

public void actor1(I_Result r) {
	// 读屏障
	// ready 是 volatile 读取值带读屏障
	if(ready) {
		r.r1 = num + num;
	} else {
		r.r1 = 1;
	}
}

三、如何保证有序性

        写屏障会确保指令重排时,不会将写屏障之前的代码排在写屏障之后,如下代码之中,保证了 num ready 的顺序不会发生变化。

public void actor2(I_Result r) {
	num = 2;
	ready = true; // ready 是 volatile 赋值带写屏障
	// 写屏障
}

        读屏障会确保指令重排序时,不会将读屏障之后的代码排在读屏障之前  

public void actor1(I_Result r) {
	// 读屏障
	// ready 是 volatile 读取值带读屏障
	if(ready) {
		r.r1 = num + num;
	} else {
		r.r1 = 1;
	}
}

四、注意

        虽然 volatile 可以保证可见性和有序性,但还是不能解决多线程环境下指令交错的问题(原子性)。即写屏障仅仅是保证之后的读能够读到最新的结果,但不能保证读跑到它前面去。而有序性的保证也只是保证了本线程内相关代码不被重排序。

五、double-checked locking 问题

        以著名的 double-checked locking 单例模式为例,如下代码:
public final class Singleton {
	private Singleton() { }
	private static Singleton INSTANCE = null;
	
	public static Singleton getInstance() { 
		if(INSTANCE == null) { // t2
			// 首次访问会同步,而之后的使用没有 synchronized
			synchronized(Singleton.class) {
				if (INSTANCE == null) { // t1
					INSTANCE = new Singleton();
				} 
			}
		}
		return INSTANCE;
	}
}

        上面这种实现的特点是:懒惰实例化,首次使用 getInstance() 才使用 synchronized 加锁,后续使用无需加锁。

        但是有一个很关键的点:第一个 if 使用了 INSTANCE 变量,是在同步代码块之外。但是在多线程环境下,上面的代码是有问题的。

        getInstance() 方法对应的字节码如下所示:

0: getstatic #2           // Field INSTANCE:Lcn/itcast/n5/Singleton;
3: ifnonnull 37
6: ldc #3                 // class cn/itcast/n5/Singleton
8: dup
9: astore_0
10: monitorenter
11: getstatic #2          // Field INSTANCE:Lcn/itcast/n5/Singleton;
14: ifnonnull 27
17: new #3                // class cn/itcast/n5/Singleton
20: dup
21: invokespecial #4      // Method "<init>":()V
24: putstatic #2          // Field INSTANCE:Lcn/itcast/n5/Singleton;
27: aload_0
28: monitorexit
29: goto 37
32: astore_1
33: aload_0
34: monitorexit
35: aload_1
36: athrow
37: getstatic #2          // Field INSTANCE:Lcn/itcast/n5/Singleton;
40: areturn

        17 表示创建对象,将对象的引用入栈 new Singleton

        20 表示复制一份对象引用

        21 表示利用一个对象引用,调用构造方法

        24 表示利用一个对象引用,赋值给 static INSTANCE

        也许 JVM 会优化为:先执行 24,再执行 21。关键在于 0: getstatic 这行代码在 monitor 控制之外,它就像之前举例中不守规则的人,可以越过 monitor 读取 INSTANCE 变量的值。

        假设此时线程一还未完全将构造方法执行完毕,如果在构造方法中要执行很多初始化操作,那么线程二拿到的是将是一个未初始化完毕的单例,就出现了问题。

六、double-checked locking 解决

        对 INSTANCE 使用 volatile 修饰即可,可以禁用指令重排,但要注意在 JDK 5 以上的版本的 volatile 才会真正有效,如下代码:

public final class Singleton {
	private Singleton() { }
	private static volatile Singleton INSTANCE = null;
	
	public static Singleton getInstance() {
		// 实例没创建,才会进入内部的 synchronized代码块
		if (INSTANCE == null) { 
			synchronized (Singleton.class) { // t2
				// 也许有其它线程已经创建实例,所以再判断一次
				if (INSTANCE == null) { // t1
					INSTANCE = new Singleton();
				}
			}
		}
		return INSTANCE;
	}
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
volatile底层实现原理是基于内存屏障(Memory Barrier)的机制。在每个volatile写操作的前面会插入一个StoreStore屏障,它保证在volatile写操作之前,所有普通写操作的结果都会被刷新到主内存中,以便对其他线程可见。而在每个volatile写操作的后面会插入一个StoreLoad屏障,它的作用是避免volatile写操作与后面可能有的volatile读/写操作重排序。 另外,在每个volatile读操作的后面会插入一个LoadLoad屏障,它用来禁止编译器将volatile读操作与下面的普通读写操作进行重排序。同时,在每个volatile读操作的后面会插入一个LoadStore屏障,它也是为了确保有序性。 总体来说,volatile关键字只能保证可见性和有序性,但无法保证原子性。如果需要保证原子性,可以使用Synchronized等锁机制来实现。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [volatile底层实现原理](https://blog.csdn.net/qq_40714246/article/details/118966064)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [volatile底层原理与实现](https://blog.csdn.net/u022812849/article/details/109257860)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

快乐的小三菊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值