浏览了一天的博文,大概总结出个人看法,以备以后继续学习
1、必须明白一个知识点,内存屏障;
下面是基于保守策略的JMM内存屏障插入策略:
在每个volatile写操作的前面插入一个StoreStore屏障。
在每个volatile写操作的后面插入一个StoreLoad屏障。
在每个volatile读操作的后面插入一个LoadLoad屏障。
在每个volatile读操作的后面插入一个LoadStore屏障。
以上就是四种内存屏障类型,加入内存屏障可以保证内存屏障前后的指令顺序执行;
个人理解,内存屏障只能保证屏障前后指令执行顺序,但是无法保证单侧指令顺序;内存屏障就好像一道道关卡,必须一关一关的过,但是在某一关你想按照什么顺序过由你自己决定,当然不能as-if-serial语义和happens-before规则。
2、happens-before规则
太多了,记住一点吧:volatile变量的写操作happens-before其他任何操作
3、as-if-serial语义
单线程环境下,指令重排序不能影响单线程执行结果
4、多线程三要素:原子性、有序性、可见性
有序但是不一定可见,原因,多线程按一定顺序执行,但是如果线程A的修改只发生在工作内存,还没有write进主存,那么线程B读取到的依旧是老数据,所以不满足可见性。
5、指令重排:优化指令运行,避免某一变量被CPU重复加载。
现在来记录synchronized 和 volatile关键字
synchronized :
1、用法:只能用在方法修饰符或者代码块上;
2、用在静态方法、或者synchronized(类.class)锁的是类的Class对象,所有类的任何对象访问的时候都会加锁;
3、synchronized(this或Object)是对Object加锁,同一对象访问会生效cpu不会在线程之间切换,不同对象访问不会生效;
4、属于可重入锁;
5、加锁时工作内存失效,区主存读取数据;
6、释放锁之前,先把工作内存数据刷新到主存。
7、为什么synchronized 内对象new的过程中,初始化与赋值会重排序,因为在关键字内部,所有可以重排。
volatile:
1、用法:只能用在变量上;
2、根据happens-before规则,保证内存屏障前后执行顺序;
3、volatile变量修改之后会立即刷新到主存,并使其他线程的工作内存失效,要求其他线程强制到主存读取数据;
4、为什么volatile线程不安全?
i++,线程a、b同时读取1,a加一后,更新到主存变成2,并使b工作内存失效,但是b已经读取到i的值,不会再去主存读取,那么通过b也会更新主存为2,就导致数据不一致。
二者对比:
相同点:
1、二者都会在编译期往字节码中插入内存屏障;
2、都可以保证及时更新主存,个人理解:操作依旧发生在工作内存,大家都说会及时更新到主存,name我觉得这两个关键字由于内存屏障的原因会把写工作内存,写主存这两步变成不可分割的原子操作,主存更新之前,是不允许其他线程读的。
不同点:
1、synchronized原子性、有序性、可见性
有序性只能保证被synchronized锁住的一个块与另外一个块之间的有序,并不能保证块内部指令的有序。
2、volatile有序性,可见性
有序性只能保证变量的读写有序,那么变量构造的顺序无法保证。
3、两者都会插入内存屏障,但是synchronized关键字会在外围在插入monitorenter 和 monitorexit指令,来排斥其他线程,这就是锁。
4、可见synchronized加锁,volatile无锁,所有性能高。
最后一直不明白双重检查单例模式为什么synchronized 和 volatile 要一起使用,现在解开疑惑了:
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
synchronized保证多线程之间有序进行,但是在new 对象的时候,初始化与赋值会重排序,另外一个线程来读的时候,可能会读到一个没有初始化的对象。加上volatile关键字以后,可以确保另外一个线程的读操作一定是发生在new 对象的所有步骤完成之后,就不会有对象“逸出”的问题。
以前一直以为是volatile确保new 对象的赋值操作一定是最后一步,发现并不是这样的。
看了一天越看越乱,就暂时这样理解:
synchronized 和 volatile声明之后,被包裹的变量或代码变成一个原子,一个个原子直接会顺序执行,不会交叉执行。只不过synchronized 包裹代码,无法解决变量之间的交叉执行,volatile包裹变量,可以确保变量的顺序执行。