CAS和Synchronized

CAS -> unsafe -> CAS底层思想 -> ABA --> 原子引用更新 --> 如何规避ABA

CAS compareAndSwap 原理

CAS(V,E,N) V表示要更新的变量(内存值)   E表示预期值   N表示新值 (当前值和底层值一样时候,才更新)

传入的值是工作内存,底层的值是主内存,工作内存和主内存不一定是同步的,在某一时间点会同步相同。所以CAS不断尝试,等待他们相同后在操作。

网上的方法实现(理解即可)

public final int getAndAddInt(int delta) {
for (;;) {
int current = get();
int next = current + delta;
if (compareAndSet(current, next))
return next;
}
}
假设delta的值为1,在CAS算法下操作的话,首先进入一个for循环体;假设存在着两个线程,并且内存中的值value=3;根据Java内存模型,每一个线程都存在这这个变量的副本;
    1) 线程1进入循环体,获取到current的值为3,然后获取到到next的值此时为4;此时假设线程1运气不好,被挂起;
    2) 线程2进入循环体,获取到current的值为3,同时next的值也为4;线程2运气好,此时继续执行。 线程2传入两个参数,一个当前值,以及一个预期值;当前值,也就是current=3.要修改成为4;此时当前值也就是预期值和内存中的value比较,此时都是3,那么修改内存中的值为4;
    3)线程1此时再次执行compareAndSwapInt()方法的时候。发现内存中的值为4,预期的值是3,两者不相等,此时就不可以再次赋值
 

CAS的问题

1. 循环时间长开销大: 自旋CAS(不成功,就一直循环执行,直到成功), 并发高的时候耗费CPU.

2. ABA问题  : 如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化。解决思路使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。Java8 AtomicInteger 使用 AtomicStampReference 来解决增加版本号。

3. 只能保证一个共享变量的原子操作

  CAS与Synchronized的使用情景:   

1、对于资源竞争较少(线程冲突较轻)的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源;而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能。

2、对于资源竞争严重(线程冲突严重)的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized。

补充: synchronized在jdk1.6之后,已经改进优化。synchronized的底层实现主要依靠Lock-Free的队列,基本思路是自旋后阻塞,竞争切换后继续竞争锁,稍微牺牲了公平性,但获得了高吞吐量。在线程冲突较少的情况下,可以获得和CAS类似的性能;而线程冲突严重的情况下,性能远高于CAS。

Synchronized -  锁 类对象和实例

class Test{
// 以下两个方法是等价的,都是锁住了 new Test()的实例
public void a0() {
synchronized(this) {
}
} public synchronized void a() {
}
}

锁住类

class Test {
// 以下两个方法是等价的
public static void a() {
// 因为是静态方法,共享与不同的线程,所以这里需要锁住整个Test类型,不再是实例
synchronized(Test.class) {
}
} public synchronized static void a() {
}
}

子类不会继承父类的Synchronize方法, 因为Synchronized不属于方法签名。

参考文档

https://www.cnblogs.com/Leo_wl/p/6899716.html

https://blog.csdn.net/tiandao321/article/details/80811103

https://www.cnblogs.com/gosaint/p/9045494.html

https://blog.csdn.net/mmoren/article/details/79185862

上一篇:Java知多少(93)鼠标事件


下一篇:Activity相关知识点总结