synchronized是悲观锁:
每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁。
CAS操作的就是乐观锁(乐观锁在Java中的使用,是无锁编程,常常采用的是CAS算法,典型的例子就是原子类):
每次不加锁而是假设没有冲突而去完成某项操作,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据。
如果因为冲突失败就重试,直到成功为止。
悲观锁适合写操作非常多的场景,乐观锁适合读操作非常多的场景
CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程。
并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题
Java中CAS操作的执行依赖于Unsafe类的方法,Unsafe类中的方法都直接调用操作系统底层资源执行相应任务
CAS机制当中使用了3个基本操作数:
内存地址V(主内存中存放的V值,所有线程共享)
旧的预期值A(线程上次从内存中读取的V值A存放在线程的帧栈中,每个线程私有)
要修改的新值B(需要写入内存中并改写V值的B值)
更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,
才会将内存地址V对应的值修改为B(防止多线程并发问题)。
1.CPU开销较大
在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很大的压力。
2.不能保证代码块的原子性
CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性。
比如需要保证3个变量共同进行原子性的更新,就不得不使用Synchronized了。
3.ABA问题
CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,
但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。
ABA问题的解决思路就是使用版本号:
在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。
java.util.concurrent.atomic:该包中提供了许多基于CAS实现的原子操作类
AtomicInteger类中所有自增或自减的方法都间接调用Unsafe类中的getAndAddInt()方法实现了CAS操作,从而保证了线程安全
//Unsafe类中的getAndAddInt方法
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!compareAndSwapInt(o, offset, v, v + delta));
return v;
}
getAndAddInt()通过一个while循环不断的重试更新要设置的值,直到成功为止
原文:https://www.cnblogs.com/loveer/p/11502521.html