悲观锁:
多个线程尝试获取同步资源的锁(又或者说是给同步资源加锁)
- 加锁成功:操作资源,当执行完毕后,释放锁,然后 CPU 会唤醒等待的线程再次尝试获取锁
- 加锁失败:进入等待队列,当 CPU 唤醒后再次尝试获取锁
乐观锁:
线程直接获取同步资源数据,执行各自的操作,但是在更新内存中的同步资源之前先判断资源是否被其他线程修改,据此进行不同的操作:
- 同步资源没有被更新:更新内存中同步资源的值
- 同步资源被其他线程更新:根据实现方法执行不同的操作(报错 or 重试)
什么是CAS?
CAS(Compare And Swap),即比较并交换,它的功能是判断内存某个位置的值是否为预期值,如果是则更新为新的值,这个过程是原子的。
当多个线程尝试使用 CAS 同时更新一个变量时,只有其中一个线程能更新变量的值,而其他线程都失败,失败的线程不会被挂起,而是被告知在这次竞争中失败,并且可以再次尝试。
CAS 操作中包含三个操作数 —— 需要读写的内存位置(V)、进行比较的预期原值(A)和拟写入的新值(B)。如果内存位置 V 的值与预期原值 A 相匹配,那么处理器会自动将该位置值更新为新值 B,否则处理器不做任何操作。CAS 是通过无限循环来获取数据的,若果在第一轮循环中,t1 线程获取地址里面的值被 t2 线程修改了,那么 t1 线程需要自旋,到下次循环才有可能机会执行。
1 | public void add(int num) { |
CAS缺点
- 循环时间长,开销大。如果CAS失败,会一直进行尝试.如果CAS长时间一直不成功,可能会给CPU带来很大的开销
- 只能保证一个共享变量的原子性
- 产生ABA问题
ABA问题
1 | sequenceDiagram |
如上图所示,在线程 t1 从内存中取出变量值 A,这时另外一个线程 t2 也从内存中取出变量值 A,并且线程 t2 进行了一些操作将内存中变量值变成了 B,然后线程 t2 又将内存中变量值变成了 A,这时候线程 t1 进行 CAS 操作发现内存中仍然是 A,然后线程 t1 操作成功。
尽管线程 t1 的 CAS 操作是成功的,但是不代表这个过程就是没有问题的。
解决方法:
- 使用 AtomicStampedReference 类,可以记录更新操作的时间戳版本号;
- 使用 AtomicMarkableRenference 类,根据标志位来判断是否更换过。