volatile 与 synchronized 的比较
1、volatile
(1)可见性
写操作:
对volatile变量的写操作,会紧跟一个LOCK前缀指令,LOCK前缀的指令效果是把工作内存中的变更(可以理解为写缓冲区)都写到主内存中,并且使得其他cpu关于这部分数据的缓存行也都失效(MESI机制)。其他线程(cpu)读取这部分变量时,就会从主内存中重新读取。这样就保证了这部分数据的可见性。而volatile变量自然也被包含在这部分数据中,毕竟刷回主内存本身就是volatile的作用。
读操作:
如果线程A读取了一个volatile变量,则读取该volatile变量时线程A可见的所有变量也将从主内存中重新读取。(读取了一个volatile变量,则当前线程A的读缓存失效(清空),后续使用共享变量时需要重新读取主内存)
(2)禁止指令重排序
2、synchronized
有三大特性:原子性,有序性,可见性
(1)原子性
多个指令作为一个整体,其他指令不会插到这个整体间,只能在这个整体执行前或者执行后才能执行。
(2)有序性
synchronized保证的有序性是多个线程之间的有序性,即被加锁的内容要按照顺序被多个线程执行
synchronized 并没有禁止指令重排序的功能,也就是说synchronized 代码块中的代码指令还是会指令重排序。
但synchronized 是怎么保证有序性的呢?
具体解释请看:https://www.cnblogs.com/hollischuang/p/11386988.html
所以单例模式中使用双重检查之后(第二重检查还加了synchronized),也还是不能保证可见性和有序性,还需要给字段加volatile才能保证。
(3)可见性
在Java内存模型中,synchronized规定,线程在加锁时,
获取锁→先清空工作内存→在主内存中拷贝最新变量的副本到工作内存→执行完代码→将更改后的共享变量的值刷新到主内存中→释放锁
(注意:加锁与解锁需要是同一把锁)
通过以上两点,可以看到synchronized能够实现可见性。但是有个明显的缺点:synchronized代码块只保证在释放锁前才把对共享变量的修改刷新回主内存。。而在同步代码块执行期间对共享变量的修改,还在暂存于工作内存中,并不是立即刷到主内存的,对其他线程不一定可见。(单例模式双重检查加volatile的原因之一)
所以synchronized的可见性并不是真正的可见性。volatile才是。
可以参考这几篇文章:
https://blog.csdn.net/singwhatiwanna/article/details/104421561/
https://www.zhihu.com/question/337265532/answer/794398131
https://www.cnblogs.com/hollischuang/p/11386988.html