多线程与高并发编程(二)
一、Volatile
- 保证线程可见性
MESI 缓存一致性协议(CPU级别) - 禁止指令重排序
DCL单例(Double Check Lock双重检查锁)
问:在双重检查锁的单例模式中要不要加volatile?
答:要加,一般情况下不加volatile结果仍然不会出问题,【
补充知识:
new对象时分为三步:
①给对象申请空间(int类型对象初始设为0)
②给对象的成员变量初始化(int类型对象把需要的值赋给这个对象)
③将空间赋值给新对象(int对象指向空间),此步结束new的对象就不为空了】,但是在如果是int类型,不加volatile进行了指令重排序,那么可能在第一步申请对象空间a=0之后,直接先进行第三步指向a,这样的话在第一步第三步之间第二个线程进来发现对象不为空直接拿走,然后才a=8修改,这样拿到的是0,不是想要的8,加了volatile防止指令重排序,这样指令顺序就不会变了。
loadfence原语指令,storefence原语指令,读写屏障 cpu级别支持的。
注:volatile不能保证原子性,也就是说volatile不能替代synchronized。
volatile尽量不要修饰引用类型,引用指向的对象内的成员变量的改变是不会被观察到的。
二、synchronized优化(锁优化)
- 锁的细化
如果只需要一小块加锁,那就不要加载方法上 - 锁的粗化
如果方法内锁的东西特别多,整个方法加入锁
注:synchronized锁的对象不能再改变最好加final,若对象改变了多线程发生问题
三、CAS(无锁优化 自旋)
- CompareAndSwap(jdk老版本,目前8中) CompareAndSet(jdk新版本的) CAS是CPU原语支持的
atomic(原子的),以这个词开头的都是以CAS来保证了线程安全的(都是原子性) - CAS可以视作是一个方法
cas(v, expected, newValue),v代表修改的对象,expected代表期望原本的值,newValue代表改完的新值。
先拿到v,等到要修改时读取一下expected,若V的值等于expected则改为newValue。
例:原本a=0,此时要a++,cas中v=0,expected=0,newValue=0+1,若v == expected则改为newValue,若不等(比如有别的线程已经++了)则重新再试一遍读取v=1,expected=1且v==expected改为newValue=1+1。
cas是CPU原语支持的所以不能被打断操作 - ABA问题
cas时另一个线程改了值后来又改回来了,cas判断还是原来的值所以直接进行下去,如何解决?
若为int等则不用理会,若为引用类型则每次更改都加版本号version,每次cas都判断version。(atomicStamped…中有version判断的) - Unsafe 简单了解
cas在unsafe中完成的
他是个典型的单例模式
这个类不能直接用只能反射用它
基本等同于c c++的指针,直接分配内存的类