赞
踩
CAS全称Compare And Swap(比较与交换),是一种无锁编程算法,能够完全避免锁竞争带来的体统开销问题,也能够避免CPU在多个线程之间频繁切换和调度带来的开销。从某种程度上说,CAS比加锁机制具有更优的性能。
CAS能够在不阻塞的前提下,以原子性的方式来更新共享变量的数据,也就是在更新变量的数据时,能够保证现成的安全性。CAS算法一般会涉及3个操作数据,分别为:
Unsafe类是java中实现CAS操作的底层核心类,提供了硬件级别的原子性操作,在Unsafe类中,提供了大量的native方法,通过JNI的方式调用JVM底层C和C++实现的函数。在java中的java.util.concurrent.atomic包下的原子类,底层都是基于Unsafe类实现的。
使用Unsafe类获取UnsafeTest类中的静态变量staticNmae和成员变量memberVariable的偏移量,然后通过Unsafe类中的putObject()方法直接修改staticName的值,通过compareAndSwapObject()方法修改memberVariable的值
package com.lifly; import sun.misc.Unsafe; import java.lang.reflect.Field; /** * @author lifly * @description * @date 2023-04-06 22:06 * @versoin 1.0.0 **/ public class UnsafeTest { private static final Unsafe unsafe = getUnsafe(); private static long staticNameOffset = 0; private static long memberVariableOffset = 0; private static String staticName = "lifly_001"; private String memberVariable = "lifly_001"; static { try { staticNameOffset = unsafe.staticFieldOffset(UnsafeTest.class.getDeclaredField("staticName")); memberVariableOffset = unsafe.objectFieldOffset(UnsafeTest.class.getDeclaredField("memberVariable")); }catch (NoSuchFieldException e){ e.printStackTrace(); } } public static void main(String[] args) { UnsafeTest unsafeTest = new UnsafeTest(); System.out.println("修改前的值如下:"); System.out.println("staticName"+staticName+",memberVariableOffset"+unsafeTest.memberVariable); unsafe.putObject(UnsafeTest.class,staticNameOffset,"lifly_static"); unsafe.compareAndSwapObject(unsafeTest,memberVariableOffset,"lifly_001","lifly_variable"); System.out.println("修改后的值如下:"); System.out.println("staticName"+staticName+",memberVariableOffset"+unsafeTest.memberVariable); } private static Unsafe getUnsafe(){ Unsafe unsafe = null; try { Field singleOneInstanceField = Unsafe.class.getDeclaredField("theUnsafe"); singleOneInstanceField.setAccessible(true); unsafe = (Unsafe) singleOneInstanceField.get(null); }catch (Exception e){ e.printStackTrace(); } return unsafe; } }
在上述代码中,首先通过getUnsafe()方法获取Unsafe实例对象,然后定义了两个long类型的静态变量staticNameOffset和memberVariableOffset,分别表示静态变量staticName和成员变量memberVariable的偏移量。
接下来,定义了一个String类型的静态变量staticName,初始值为lifly_001,定义一个String类型的成员变量memberVariable,初始值为lifly_001。
然后在静态代码块中调用Unsafe类中的putObject()方法修改静态变量staticName的值,调用Unsafe类的compareAndSwapObject()方法修改成员变量memberVariable的值。预期的结果是将静态变量staticName的值修改为lifly_static,将成员变量memberVariable的值修改为lifly_variable,如图所示,结果也符合预期。
设置20个线程并行运行,每个线程通过都通过CAS自旋的方式对一个共享变量的数据进行自增操作,每个线程运行的次数为500次,最终得出的结果为10000
package com.lifly; import sun.misc.Unsafe; import java.lang.reflect.Field; import java.util.concurrent.CountDownLatch; import java.util.stream.IntStream; /** * @author lifly * @description * @date 2023-04-08 13:59 * @versoin 1.0.0 **/ public class CasCountIncrement { /** * 获取unsafe对象 */ private static final Unsafe unsafe = getUnsafe(); /** * 现成的数量 */ private static final int THREAD_COUNT = 20; /** * 每个线程运行的次数 */ private static final int EXECUTE_COUNT_EVERY_THREDA = 500; /** * 自增的count值 */ private volatile int count = 0; /** * count的偏移量 */ private static long countOffset; /** * 在静态代码块中通过Unsafe类的objectFieldOffset()方法获取count变量的偏移量。将其赋值给countOffset静态变量 */ static { try { countOffset = unsafe.objectFieldOffset(CasCountIncrement.class.getDeclaredField("count")); }catch (NoSuchFieldException e){ e.printStackTrace(); } } /** * 以CAS的方式对count值进行自增操作 */ public void incrementCountByCas(){ //将count的值赋值给oldCount int oldCount = 0; do { oldCount = count; }while (!unsafe.compareAndSwapInt(this,countOffset,oldCount,oldCount+1)); } /** * 获取Unsafe实例对象 * @return unsafe */ private static Unsafe getUnsafe() { Unsafe unsafe = null; try { Field singleoneInstanceField = Unsafe.class.getDeclaredField("theUnsafe"); singleoneInstanceField.setAccessible(true); unsafe = (Unsafe) singleoneInstanceField.get(null); } catch (NoSuchFieldException | IllegalAccessException e) { throw new RuntimeException(e); } return unsafe; } /** * 创建main方法,循环20次,在每次循环中创建一个线程,每个线程中调用incrementCountByCas方法,同时在每个线程执行完毕, * 都调用CountDownLatch的countDown方法使计数器减1. * 然后再main方法中调用countDownLatch类的await方法阻塞main线程,直到CountDownLatch中的计数器减为0,才继续执行 * 最后,打印count的最终结果数据 * @param args * @throws InterruptedException */ public static void main(String[] args) throws InterruptedException { CasCountIncrement casCountIncrement = new CasCountIncrement(); CountDownLatch countDownLatch = new CountDownLatch(THREAD_COUNT); IntStream.range(0,THREAD_COUNT).forEach((i)->{ new Thread(()->{ IntStream.range(0,EXECUTE_COUNT_EVERY_THREDA).forEach((j)->{ casCountIncrement.incrementCountByCas(); }); countDownLatch.countDown(); }).start(); }); countDownLatch.await(); System.out.println("count的最终结果为:"+casCountIncrement.count); } }
可以看到,count的最终结果为10000,与程序的预期结果符合。
ABA问题简单来说就是一个变量的初始值为A,被修改为B,然后再次被修改为A了。在使用CAS算法进行检测时,无法检测出A的值是否经历过被修改为B,又再次被修改为A的过程。
由上图可以看出,当线程A准备调用CAS(1,2)将变量V的值由1修改为2时,CPU发生了线程切换,切换线程B上,线程B在执行业务逻辑的过程中,调用CAS(2,1)将变量V的值由1修改为了2,又调用CAS(2,1)将变量V的值由2修改为了1.然后CPU又发生了线程切换切换到了线程A上,执行CAS(1,2)将变量V的值由1修改为2.虽然线程A的CAS操作能够执行成功,但是期间线程B已经修改了变量V的值了,从而造成了ABA问题。
ABA问题最典型的解决方案就是使用版本号问题。具体的操作方法就是在每次修改数据时都携带一个版本号,只有当该版本号与数据的版本号一致时,才能执行数据的修改,否则修改失败。因为操作的时候携带了版本号,而版本号在每次修改时都会增加,并且只会增加不会减少,所以可以有效的避免ABA问题。
在java.util.concurrent.atomic包下提供了AtomicStampedReference类和AtomicMarkableReference类以解决ABA问题。
AtomicStampedReference类在CAS的基础上增加了一个类似于版本号的时间戳,可以将这个时间戳作为版本号来防止ABA问题,例如AtomicStampedReference类中的warkCompareAndSet()方法和compareAndSet(),AtomicStampedReference类中的warkCompareAndSet()方法和compareAndSet()反复噶的源码如下:
public boolean weakCompareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) { return compareAndSet(expectedReference, newReference, expectedStamp, newStamp); } public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) { Pair<V> current = pair; return expectedReference == current.reference && expectedStamp == current.stamp && ((newReference == current.reference && newStamp == current.stamp) || casPair(current, Pair.of(newReference, newStamp))); }
从源码中可以看出,AtomicStampedReference类中的weakCompareAndSet()方法内部调用的是compareAndSet()方法实现的。compareAndSet()方法的4个参数如下:
public boolean weakCompareAndSet(V expectedReference, V newReference, boolean expectedMark, boolean newMark) { return compareAndSet(expectedReference, newReference, expectedMark, newMark); } public boolean compareAndSet(V expectedReference, V newReference, boolean expectedMark, boolean newMark) { Pair<V> current = pair; return expectedReference == current.reference && expectedMark == current.mark && ((newReference == current.reference && newMark == current.mark) || casPair(current, Pair.of(newReference, newMark))); }
从源码中可以看出,在AtomicMarkableReference类的weakCompareAndSet()方法compareAndSet()方法的实现中,增加了boolean类型的参数,只判断对象是否被修改过。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。