CAS与synchronized
1.CAS
1.CAS的优化过程
CAS也叫做比较和交换
会把寄存器A的值和内存M进行比较,如果相同,就把寄存器B中的值与内存的值交换(只关心交换后内存M的值,不关系寄存器B中的值),如果不相同,什么也不做。
CAS的操作是由CPU的一条指令来完成的,所以它是原子性的,线程安全并且效率较高。
2.CAS常用场景
1.实现原子类:
比如count++这个操作,本来是三个指令来完成,这是线程不安全的,所以要加锁才可以线程安全,如果使用了CAS,就不用加锁,性能得到了提升。保证了线程安全&高效。
2.实现自旋类:
纯用户态的轻量级锁,锁被线程占用时,另外的线程不挂起等待,而且反复的查看锁释放被释放,这样就可以第一时间获取到锁信息。
3.CAS的ABA问题
在CAS中 进行比较的时候,当寄存器A的值和内存M比较的时候,如果相等,会进行交换,但是相等的时候,我们不知道内存M的值是一直没有改变的,还是变了然后又变回来了。
极端情况下 会出现问题,比如:在带外卖的时候,网卡,我多点了几次下单,本应该只下单一次扣一次钱,但是ABA问题就导致我这多点的几下,就会多创建几个线程,来扣钱。
下面我举个例子:
我多点了两次,创建了t1,t2线程,t1线程获取到我的余额100块,然后扣钱20,这时我的余额就只有80了,但是再没扣钱的时候,t2线程也获取了我的余额100块,然后对照着我的余额80,已经不是之前的100块了,就不会扣钱。看起来CAS没出现问题。
但是如果在这个时候有人t2线程扣款之前,给我转了20块到余额里,这时t2在CAS的时候比较,就发现余额是100,相同,然后扣钱,这样就导致扣了两次钱了。因为它不能判断你当前的余额,是变了几次变回来了,还是压根没变。
为了解决CAS的ABA问题:
如果有一个记录,来记录我内存中的数据变了几次,然后再进行CAS的时候不传那个数据的值了,传记录的值,这样就知道在过程中数据是有变化过的。
如何进行记录?
使用另一块内存N,保存M内存的修改次数,或者修改时间,前提是这些都是只增加的,这样就可以解决问题。
修改的时候,看一下寄存器A中的记录值和内存M的记录值相同吗,如果相同,就可以修改,否则说明内存M在CAS前是有变化的,不能修改。
2.synchronized原理
1.锁升级/锁膨胀
synchronized的用处是加锁,会涉及到线程的阻塞等待,所以synchronized内部实现了 “自适应”。
1.偏向锁
2.轻量级锁
3.重量级锁
在加锁的过程中,如果不存在锁竞争,就使用偏向锁(不是上锁,而是设置一种状态),如果存在锁竞争但不激烈,就使用轻量级锁,如果存在锁竞争并且激烈,就使用重量级锁,这就是JVM实现synchronized时,为了方便使用,做出的优化。
2.锁消除
JVM自动判定,当前上锁的代码块,如果不需要锁的话,就会自动消除锁,但是这个操作时JVM有100%的把握判断锁没用的时候,才会消除,否则不会。
3.锁粗化
说到锁粗化就有一个概念叫:锁的粒度
synchronized对应的代码块中,包含的代码少,锁的粒度就细,包含的代码多,锁的粒度就粗。
所以在不影响代码逻辑的前提下,对粒度细的锁进行粗化。(比如任务1,2,3)每个任务都上了锁,但是加锁是有开销的。所以在不影响代码逻辑的前提下,将锁范围放大(包含的代码增多),只在任务1加锁 一直到任务三再解锁,这样就做到了锁粗化