想了解更多JUC的知识
——JUC并发编程合集
1. CAS的概述
- CAS的全称为
Compare-And-Swap
(比较并交换),它是一条CPU并发原语,比较工作内存值(预期值)和主物理内存的共享值是否相同,相同则执行规定操作,否则继续比较直到主内存和工作内存的值一致为止。这个过程是原子的- AtomicInteger类主要利用CAS(compare and swap)+volatile和native方法来保证原子操作,从而避免synchronized的高开销,执行效率大为提升。
-
CAS并发原语体现在Java语言中,就是
sun.misc
包下的UnSafe类中的各个方法。调用UnSafe类中的CAS方法,JVM会帮我实现 CAS汇编指令。这是一种完全依赖于硬件功能,通过它实现了原子操作,再次强调,由于CAS是一种系统原语,原语属于操作系统用于范畴,是由若干条指令组成,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许中断,也即是说CAS是一条原子指令,不会造成所谓的数据不一致的问题 -
案例
public class AtomicIntegerDemo { public static void main(String[] args) { AtomicInteger atomicInteger=new AtomicInteger(5); System.out.println(atomicInteger.compareAndSet(5, 20) +"\t"+atomicInteger.get()); //true 20 System.out.println(atomicInteger.compareAndSet(5, 22) +"\t"+atomicInteger.get()); //false 20 } }
2. UnSafe类
-
UnSafe类是CAS的核心类,由于Java 方法无法直接访问底层 ,需要通过本地(native)方法来访问,UnSafe相当于一个后门,基于该类可以直接操作特定的内存数据。UnSafe类在于sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存,因为Java中CAS操作依赖于UnSafe类的方法。
- UnSafe类中所有的方法都是
native
修饰的,也就是说UnSafe类中的方法都是直接调用操作底层资源
执行响应的任务
- UnSafe类中所有的方法都是
-
变量
ValueOffset
,便是该变量在内存中的偏移地址,因为UnSafe就是根据内存偏移地址获取数据的 -
volatile修饰的变量
value
,保证了多线程之间的可见性 -
Unsafe (不是指线程不安全,是指过于底层,不建议编程人员直接使用)对象提供了非常底层的,操作内存、线程的方法,Unsafe 对象不能直接调用,只能通过反射获得。LockSupport的
park
方法,cas相关的方法底层都是通过Unsafe类来实现的。
3. CAS的缺点
-
循环时间长开销很大
- 在getAndAddInt方法执行的时候,有个do-while循环,使用的是
自旋锁
,如果CAS失败,就会一直尝试,直到成功。如果长时间失败,可能会给CPU带来很大的开销。
- 在getAndAddInt方法执行的时候,有个do-while循环,使用的是
-
只能保证一个共享变量的原子性
- 当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作
- 对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子性
-
ABA问题的产生
-
比如一个线程A从内存位置V中取出 i,这时候另一个线程B也从内存中取出 i,并且线程B进行了一些操作将值变成了 j,然后线程B又将V位置的数据变成i ,这时候线程A进行CAS操作发现内存中仍然是i ,然后线程A操作成功。尽管线程A的CAS操作成功,但是不代表这个过程就是没问题的
- 线程A:期望值是1,新值为2;
- 线程B:两个操作:
- 期望值是1,变成3
- 期望值是3,变成1
- 所以对于线程A来说,i 的值还是1,所以就出现了问题,骗过了线程A;
-
4. 解决ABA问题
-
解决ABA问题,对应的思想:就是使用了乐观锁,即带
版本号
的原子操作 -
解决方案:使用AtomicStampedReference每修改一次都会有一个版本号
注意:
Integer 使用了对象缓存机制,默认范围是-128~127,推荐使用静态工厂方法valueOf获取对象实例,而不是new,因为valueOf使用缓存,而new一定会创建新的对象分配新的内存空间。
Interger对-128~127的缓存这个范围才有效,不在这个范围comareAndSet会一直返回false。
-
解决案例
public class ABATest { /** * AtomicStampedReference * 注意:如果泛型是一个包装类,注意对象的引用问题(正常在业务操作,这里面比较的都是一个个对象) * 第一个参数:初始值 * 第二个参数:初始版本号 */ static AtomicStampedReference<Integer> atomicReference = new AtomicStampedReference<>(100, 1); public static void main(String[] args) { new Thread(() -> { //获得版本号 System.out.println("a1===version-" + atomicReference.getStamp()); //暂停2s try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } //第一次修改,将版本号+1 /** * atomicReference.compareAndSet() * 参数1:期待值 参数2:新值 * 参数3:期待版本号 参数4:新版本号 */ atomicReference.compareAndSet(100,101, atomicReference.getStamp(),atomicReference.getStamp() + 1); System.out.println("a2===version-" + atomicReference.getStamp()); //第二次修改,将版本号再+1 atomicReference.compareAndSet(101,100, atomicReference.getStamp(),atomicReference.getStamp() + 1); System.out.println("a3===version-" + atomicReference.getStamp()); },"A").start(); new Thread(() -> { //获得版本号 System.out.println("b1===version-" + atomicReference.getStamp()); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } atomicReference.compareAndSet(100,101, atomicReference.getStamp(),atomicReference.getStamp() + 1); System.out.println("b2===version-" + atomicReference.getStamp()); },"B").start(); } }
输出结果: