CAS
CAS –> Unsafe –> CAS底层思想 –> ABA问题 –> 原子引用更新 –> 如何规避ABA问题
CAS底层原理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class CASDemoClass { public static void main(String[] args) { AtomicInteger atomicInteger = new AtomicInteger(5);
System.out.println(atomicInteger.compareAndSet(5, 2022));
System.out.println("当前值:" + atomicInteger.get()); } }
|
CAS(Compare and swap),即比较并交换。自旋锁或乐观锁,其中的核心操作实现就是CAS。
它的功能是判断内存中某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的。
从操作系统底层来说,CAS是一条CPU并发原语。
CAS并发原语体现在Java语言中sun.misc包下的Unsafe类,调用这个类的CAS方法,JVM会帮我们实现出CAS汇编指令。
这是一种完全依赖硬件的功能,通过这个实现了原子操作。由于CAS是一种系统原语,原语属于操作系统用语范畴,
是由若干条指令组成,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被打断,
也就是说CAS是一条CPU的原子指令,不会出现数据不一致的问题。
- AtomicInteger.getAndIncrement()方法是如何实现在多线程情况下的自增函数的线程安全的?
原因就在getAndIncrement()方法的底层实际上是CAS思想,靠的是unSafe类CPU指定原语保证原子性
①变量valueoffset表示该变量值在内存中的偏移地址,Unsafe类可以通过该值获取对应地址数据
②变量value使用volatile修饰,保证了多线程之间的数据可见性
1 2 3 4 5 6 7 8
| public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1; }
|

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
public final int getAndAddInt(Object var1,long var2,int var4){ int var5; do{ var5 = this.getIntVolatile(var1,var2); }while(!this.compareAndSwapInt(var1,var2,var5,var5+var4)); return var5; }
|
那么这个Unsafe类又是什么呢?
Unsafe类是在sun.misc包下,不属于Java标准。但是很多Java的基础类库,包括一些被广泛使用的高性能开发库
都是基于Unsafe类开发的,比如Netty、Cassandra、Hadoop、Kafka等。Unsafe类在提升Java运行效率,
增强Java语言底层操作能力方面起了很大的作用。Unsafe类使Java拥有了像C语言的指针一样操作内存空间的能力,
同时也带来了指针的问题。
- Unsafe类中的所有方法都是用native修饰的,也就是说Unsafe类中的方法都是直接调用操作系统底层资源执行任务。
CAS的缺点
1.循环时间长
因为getAndAddInt方法执行时,有个do whlie,
如果CAS失败会一直进行尝试,长时间不成功就会给CPU代来很大的开销
2.只能保证一个共享变量的原子操作
3.引出ABA问题
ABA问题
当前内存的值一开始是A,被另外一个线程先改为B然后再改为A,那么当前线程访问的时候发现是A,则认为它没有
被其他线程访问过,又进行其的修改行为。看起来,这一切没有问题,实际上过程中是发生了很多变化,而在某些
场景下这样是存在错误风险的。
那么如何解决这个ABA问题呢,大多数情况下乐观锁的实现都会通过引入一个版本号标记这个对象,每次修改版本号
都会变化,比如使用时间戳作为版本号,这样就可以很好的解决ABA问题。
在JDK中提供了AtomicStampedReference类来解决这个问题,思路是一样的。这个类也维护了一个int类型的标记stamp,
每次更新数据的时候顺带更新一下stamp。
自旋锁
自旋锁(SpinLock)是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁。
好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference;
public class SpinLockDemoClass {
AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void myLock(){ Thread thread = Thread.currentThread(); System.out.println("当前进入的线程名为: " + thread.getName()); while (!atomicReference.compareAndSet(null,thread)){
} }
public void myUnLock(){ Thread thread = Thread.currentThread(); atomicReference.compareAndSet(thread,null); System.out.println("当前进入的线程名为:"+ thread.getName() + "释放锁"); }
public static void main(String[] args) { SpinLockDemoClass spinLockDemoClass = new SpinLockDemoClass();
new Thread(() ->{ spinLockDemoClass.myLock(); try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } spinLockDemoClass.myUnLock(); },"AA").start();
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(() ->{ spinLockDemoClass.myLock(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } spinLockDemoClass.myUnLock(); },"BB").start();
} }
|