多线程与高并发(二)--synchronized,volatile,CAS
synchronized底层
- JDK早期的实现是重量级锁(向操作系统申请锁)。
- Hotspot实现(锁升级)
- 首先在锁的markword上记录第一个访问的线程ID,并没有上锁(偏向锁)。
- 如果有线程争用,升级为自旋锁。
- 默认旋10次之后升级为重量级锁,未获得锁的线程进入等待队列。
- 可参考http://ifeve.com/java-synchronized/
引申:
-
- 在Hotspot锁只能升级,不能降级。
- 自旋锁只占用CPU,不访问操作系统。在用户态解决锁问题,不经过内核态,加解锁效率比重量级锁效率高。
- 执行时间长以及线程多的时候尽量使用重量级锁。
synchronized 优化
- 锁细化:同步代码块中的语句越少越好。
- 锁粗化:如果一个方法中加了很多锁,这时把整个方法上锁性能会提高很多。
- 锁对象要加final
Volatile
- 保证线程可见性
- 所有线程共享堆内存,另外每个线程都有自己的工作内存。当线程要访问堆内存中的数据时,会把堆内存中的数据copy到线程的工作内存,对该变量的改变是在工作内存的进行改变。线程对变量的更改,没有及时的反应到其他线程,导致线程之间数据不可见。
- 变量加上volatile关键字之后,能够保证一个线程修改变量之后,其他线程及时可以发现。
- volatile本质上使用了CPU的缓存一致性协议(MESI)。
- 禁止指令重排序
- CPU为了提高执行效率,会并发的执行指令。使用volatile之后,CPU不会进行重排序。
- DCL单例(Double Check Lock)
package designPatterns; public class DCLSingleTon { private volatile static DCLSingleTon dclSingleTon; private DCLSingleTon() { } public static DCLSingleTon getInstance() { // 外层的判断不能去掉,可以显著提高效率 if (dclSingleTon == null) { synchronized (DCLSingleTon.class) { if (dclSingleTon == null) { /** * 经过编译之后分为三步 * 1.分配内存,给变量赋默认值 * 2.成员变量初始化 * 3.把内存赋值给dclSingleTon * 如果发生指令重排序,可能会发生2和3交换 * 这时dclSingleTon已经不是null,新来的线程就会直接获得未初始化成员变量的instance */ dclSingleTon = new DCLSingleTon(); } } } return dclSingleTon; } }
- DCL单例(Double Check Lock)
- CPU为了提高执行效率,会并发的执行指令。使用volatile之后,CPU不会进行重排序。
- 不能保证原子性
CAS(Compare And Swap)
- cas(V, Expected, NewValue)
- if V == Expected
V = NewValue
otherwise try again or fail
-
- concurrent包中的cas是CPU原语支持,执行过程不会被打断
- ABA问题
-
当前线程的CAS操作无法分辨当前V值是否发生过变化。比如在你非常渴的情况下你发现一个盛满水的杯子,你一饮而尽。之后再给杯子里重新倒满水。然后你离开,当杯子的真正主人回来时看到杯子还是盛满水,他当然不知道是否被人喝完重新倒满。
- 可以通过加版本号解决。
-
- LongAdder与AtomicLog比较
- 两者都是使用的CAS操作来进行增减
- 不同点在于LongAdder内部是分段锁。

浙公网安备 33010602011771号