java 多线程 volatile 和 synchronized 分析

在分析之前,我们需要先了解以下并发三特征,从这三个特征出发,我们来看看volatile和synchronized的差别。

并发三特征

原子性

指一些操作只能同时执行成功或执行失败,整个操作不可分割。

举个简单的例子,往ATM中存钱,存入500,账户余额加500,存入和余额增加两个操作必须同时成功或者失败,不能存入成功,余额不增加。

synchronized

通过lock unlock可确保被锁对象内的整个操作的原子性。

volatile

只能修饰基础类型变量,所以它只能确保变量的原子性操作,比如直接赋值和读取。

int a = 5;  //原子操作

int a++;
//非原子操作,等价于
int temp = a;  //原子操作
temp = temp +1; //多线程环境中可能在这一步其他线程修改了a的值,导致线程不安全
a = temp;	//原子操作

可见性

指多线程访问一个资源(变量/对象)时,一个线程修改变资源,其他线程在进行原子性操作之前能获取到资源的最新状态。

举个简单的例子,从ATM中取钱,账户余额就500块,你取500块,当你取出500块,此时余额还未减500的是时候,你的对象(没有就new一个)在另外一个ATM上看到余额是500,也取500!于是你又有了对象,还多了500块。可谓是:并发程序不对头,对象金钱双丰收!美哉!美哉!

如何解决这个让人不想解决的问题呢?

我们要确保同一个资源,不同线程进行原子性操作执行之前获取该资源的最新状态,原子性操作之后马上更新该资源的状态。

放在上面的例子中就是:你对象 (不同线程) 在取钱 (原子性操作) 的时候,应该看到的是你取钱之后的余额 (资源的最新状态)

从这里其实我们已经可以看到,可见性应该是以原子性为基础的。

	一个原子性操作不可分割,不同线程进行原子性操作时要彼此可见。

volatile

1,当read一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。
2,当write一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量立即刷新到主内存。

synchronized

1,unlock之前将原子性操作的所有修改立即同步回主存
2,在lock之前线程工作内存中的缓存无效,重新从主内存读取

有序性

指代码执行的顺序。

正常来说,我们写的代码应该时从上到下顺序执行,但是在编译时,有时候会发生指令重排,造成代码顺序和实际我们看到的顺序不一致。编译器指令重排时满足happen before规则:

happen before规则

1, 顺序原则

一个线程内保证语义的串行性; a = 1; b = a + 1;相关的代码按顺序执行

2, volatile规则

volatile变量的写,先发生于读,这保证了volatile变量的可见性,

3, 锁规则

解锁(unlock)必然发生在随后的加锁(lock)前.

4, 传递性

A先于B,B先于C,那么A必然先于C.

5, 线程启动, 中断, 终止

线程的start()方法先于它的每一个动作.
线程的中断(interrupt())先于被中断线程的代码.
线程的所有操作先于线程的终结(Thread.join()).

6, 对象终结

对象的构造函数执行结束先于finalize()方法.

volatile修饰和synchronized修饰都是确保其原子性操作所在相对位置不发生改变。

volatile修饰原子性范围只有对变量的直接读取和赋值 相当于无法指令重排

synchronized修饰原子性范围包含整个对象/块 ,内部存在指令重排

public class TestSinglePattern {
	//double check locking
	private volatile static TestSinglePattern instance;

	public static TestSinglePattern getInstance() {
		if (instance == null) {//性能优化,排除临界点的锁机制
			synchronized (TestSinglePattern.class) {
				if (instance == null) {
					/* 新对象时分为三步 
					 * 1,开辟内存空间
					 * 2,初始化对象信息
					 * 3,返回对象内存地址
					 * 
					 * 第3步与第二部没有逻辑的先后关系,可能存在指令重排,返回一个没有初始化的对象地址
					 * 为了避免此处的错误需要volatile修饰instance,防止指令重排
					 */
					instance = new TestSinglePattern();
				}
			}
		}
		return instance;
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值