参考书:《Java并发编程艺术》
在学习这一块知识之前,可以先学习一下JMM相关的知识,回过来再看这个问题,就很好理解:https://www.cnblogs.com/perferect/p/13680158.html
volatile
特性
-
可见性:对一个volatile变量的读,总能看到任意线程对这个volatile变量最后的写入。
-
原子性:对于一个volatile修饰的变量的读/写具有原子性,但是类似于volatile++ 这种复合操作不具有原子性
注意:很多博客都写volatile变量不具有原子性,这是他们对上面原子性这段话产生了错误的理解
举个例子
volatile int a = 0;
上面也就是说:
如果只是 a =10; 这种写入操作时没有问题的;
但是 a = a + a * 10 + 1 ; 这种同时进行读和写的操作时无法保证对volatile变量操作的原子性的;
至于什么原因?
- 其实我们在学JMM的时候,也知道了,在volatile的读和写之前进行的时不同的屏障,而这种复合操作,显然是很多个步骤的读和写,需要进行“区域管理”,这也就是下面我们要了解的synchronized要做的事情了
volatile的原理
编译器重排序的顺序一致性约束
- 正如我们前面学习到的JMM中,编译器重排序,通过对volatile变量,读和写增加内存屏障来约束
转成可执行代码后的缓存一致性
- 通过Lock前缀,引起处理器缓存回写到内存
其实指是通过#LOCK指令锁定内存中的缓存并写回到内存区,并使用缓存一致性机制来保证修改的原子性。此操作被称为“缓存锁定”,缓存一致性机制会阻止同时修改有两个以上处理器缓存的内存区间。
- 利用嗅探技术,保证一个处理器的缓存回写到内存中其他处理器的缓存会变成无效。
synchronized
-
JMM编译器重排序
- 锁通过创建临界域,限制域外的代码和域内部代码的重排序,避免临界域内的变量“逸出”域。
-
生成指令执行过程中
- 底层是通过在方法的插入点通过monitorenter和monitorexit进行匹配。
synchronized中的锁:
-
对于普通同步方法,锁是当前实例对象。
-
对于静态同步方法,锁是当前类的Class对象
-
对于同步方法快,锁是Synchonized括号里配置的对象
总结
1. volatile和synchronized的区别
-
JMM编译器重排不同:
-
volatile:是通过在volatile变量的独写操作前后增加屏障实现防止重排序的
-
synchronized: 是通过增加临界区,防止临界区内变量逸出。
-
-
执行指令的不同:
-
volatile:通过#LOCK前缀,刷新数据到主内存中
-
synchronized:是通过monitor,对指令块进行监听,通过monitorenter和monitorexit进行匹配
-