首页 >> 金融 >> java并发线程透彻理解CAS以及ABA问题的处理

java并发线程透彻理解CAS以及ABA问题的处理

2023-04-21 金融

tackTrace(); } System.out.println("count:" + count); }}

以上标识符测试结果如下:

1-3-2、常用reentrantLock

常用ReentrantLock的时候一定要注意到,要将unlock()放入finally标识符块中的,可避免业务标识符所致,不能拘禁针

public class ThreadLockReentrantLock { private volatile static int count = 0; static ReentrantLock reentrantLock=new ReentrantLock(); public static void main(String[] args) { for (int i = 0; i < 10; i++) { Thread thread = new Thread(new Runnable() { @Override public void run() { //常用reentrantLock.lock();透过加针转换 reentrantLock.lock(); try { for (int j = 0; j < 10000; j++) { count++; } } finally { //reentrantLock.unlock();一定要放入finally中的,可避免业务标识符所致,避免针不拘禁 reentrantLock.unlock(); } } }); thread.start(); } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("count:" + count); }}1-3-3、常用CAS1-3-3-1、通过折射给予Unsafe

Unsafe是jdk获取的工具类,我们要常用无需通过折射有助于取到,同时给予其获取倍数的转换

public class UnsafeFactory { /** * 获取 Unsafe 普通人 * @return */ public static Unsafe getUnsafe() { try { Field field = Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); return (Unsafe) field.get(null); } catch (Exception e) { e.printStackTrace(); } return null; } /** * 获取字符串的缓可知倍数 * @param unsafe * @param clazz * @param fieldName * @return */ public static long getFieldOffset(Unsafe unsafe, Class clazz, String fieldName) { try { return unsafe.objectFieldOffset(clazz.getDeclaredField(fieldName)); } catch (NoSuchFieldException e) { throw new Error(e); } }}1-3-3-2、通过CAS借助对表达式的重写

首再行假设一个普通人Entity,其中的假设一个int各种类型表达式X,然后通过CAS对X透过重写。

public class CASTest { public static void main(String[] args) { Entity entity = new Entity(); //通过折射有助于给予Unsafe Unsafe unsafe = UnsafeFactory.getUnsafe(); //给予x缓可知中的的倍数 long offset = UnsafeFactory.getFieldOffset(unsafe, Entity.class, "x"); System.out.println(offset); boolean successful; // 4个参数分别是:普通人模板、字符串的缓可知倍数、字符串原最大值、字符串预览最大值 //通过CAS将x由0改名3 successful = unsafe.compareAndSwapInt(entity, offset, 0, 3); System.out.println(successful + " " + entity.x); //通过CAS将x由3改名5 successful = unsafe.compareAndSwapInt(entity, offset, 3, 5); System.out.println(successful + " " + entity.x); //通过CAS将x由3改名8---本条是不正式成立的,纸片之前将x改名5 successful = unsafe.compareAndSwapInt(entity, offset, 3, 8); System.out.println(successful + " " + entity.x); }}class Entity{ int x;}

运行结果如下:

首再行给予X的倍数为12,其次就是对x透过重写的结果打印。第三次重写惨败,因为第二次之前将x改名5,如果在用第一次的3作为原最大值去重写,就则会重写惨败。

1-3-3-3、CAS在结构上

根据以上可执行结果,就证明了CAS的在结构上:再行更为、后预览,这两步,底层则会借助我们借助水分子转换,有序性和可见性避免公测文切换。

1-4、CAS源码分析1-4-1、java层标识符

Unsafe类中的获取了三种CAS转换如下,这三种都是native新方法,都是Hotspot标识符,

上头用compareAndSwapInt新方法来举例说明

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

新方法参数:

1、普通人模板2、字符串缓可知最大值的倍数(根据普通人模板和倍数就可以给予完全一致的表达式)3、期望普通人模板中的的原最大值4、字符串预览最大值

1-4-2、Hotspot层1-4-2-1、Unsafe_CompareAndSwapInt新方法

线程Hotspot新方法源码如下:

#unsafe.cppUNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))UnsafeWrapper("Unsafe_CompareAndSwapInt");oop p = JNIHandles::resolve(obj);// 根据倍数,测算value的重定向jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);// Atomic::cmpxchg(x, addr, e) cas逻辑上 x:要转换的最大值 e:要更为的最大值//cas急于,来到期望最大值e,总和e,此新方法来到true //cas惨败,来到缓可知中的的value最大值,不总和e,此新方法来到falsereturn (jint)(Atomic::cmpxchg(x, addr, e)) == e;1-4-2-1-1、新方法给定

首再行线程的新方法:

Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x)

前面两个最大值是hotspot传布的最大值,后面四个最大值为java新方法传布刚才的。

其次根据倍数,测算value的重定向

//给予普通人oop p = JNIHandles::resolve(obj); // 根据倍数,测算value的重定向 jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);

然后线程Atomic::cmpxchg借助CAS转换

// Atomic::cmpxchg(x, addr, e) cas逻辑上 x:要转换的最大值 e:要更为的最大值//cas急于,来到期望最大值e,总和e,此新方法来到true //cas惨败,来到缓可知中的的value最大值,不总和e,此新方法来到falsereturn (jint)(Atomic::cmpxchg(x, addr, e)) == e;1-4-2-2、Atomic::cmpxchg新方法

无需注意到上头的标识符是Linux_x86,多种不同系统处理CAS是多种不同的

#atomic_linux_x86.inline.hppinline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) {//说明局限性可执行周边环境前提为虚拟化周边环境int mp = os::is_MP();//LOCK_IF_MP(%4) 在虚拟化周边环境下,为 cmpxchgl 命令掺入 lock 前缀,以达到缓可知屏障的效果//cmpxchgl 命令是构成在 x86 虚拟化及 IA-64 虚拟化中的的一个水分子条件命令,//它则会首再行更为 dest 指针抛出的缓可知最大值前提和 compare_value 的最大值小于,//如果小于,则双向转换 dest 与 exchange_value,否则就单上都地将 dest 抛出的缓可知最大值交给exchange_value。//这条命令启动了整个 CAS 转换,因此它也被称做 CAS 命令。originallyasmoriginally volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)": "=a" (exchange_value): "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp): "cc", "memory");return exchange_value;

cmpxchgl的详尽可执行流程:

首再行,输入是"r" (exchange_value), “a” (compare_value), “r” (dest), “r” (mp),声称compare_value可知入eax字节,而exchange_value、dest、mp的最大值可知入任意的通用字节。嵌入式汇编规定把控制器和输入字节按统一时序编号,时序是从控制器字节序列从左到右一个大以“%0”开始,分别记为%0、%1···%9。反之亦然,控制器的eax是%0,输入的exchange_value、compare_value、dest、mp分别是%1、%2、%3、%4。

因此,cmpxchg %1,(%3)其实声称cmpxchg exchange_value,(dest)

无需注意到的是cmpxchg有个比如说转换数eax,其仅仅流程是再行更为eax的最大值(也就是compare_value)和dest重定向所可知的最大值前提小于,

控制器是"=a" (exchange_value),声称把eax中的可知的最大值可知储exchange_value表达式中的。

Atomic::cmpxchg这个数组再次来到最大值是exchange_value,反之亦然,如果cmpxchgl可执行时compare_value和dest指针抛出缓可知最大值小于则则会使得dest指针抛出缓可知最大值变为exchange_value,再次eax可知的compare_value赋最大值给了exchange_value表达式,即数组再次来到的最大值是原再行的compare_value。此时Unsafe_CompareAndSwapInt的来到最大值(jint)(Atomic::cmpxchg(x, addr, e)) == e就是true,说明CAS急于。如果cmpxchgl可执行时compare_value和(dest)多达则则会把局限性dest指针抛出缓可知的最大值可知储eax,再次控制器时赋最大值给exchange_value表达式作为来到最大值,避免(jint)(Atomic::cmpxchg(x, addr, e)) == e得到false,说明CAS惨败。

传统处理器命令集虚拟化基本上都则会获取 CAS 命令,例如 x86 和 IA-64 虚拟化中的的 cmpxchgl 命令和 comxchgq 命令,sparc 虚拟化中的的 cas 命令和 casx 命令。

不管是 Hotspot 中的的 Atomic::cmpxchg 新方法,还是 Java 中的的 compareAndSwapInt 新方法,它们其本质上都是对确切来说游戏平台的 CAS 命令的一层简单封装。CAS 命令作为一种硬件内联,有着天然的水分子性,这也正是 CAS 的价最大值所在。

1-5、通过CAS借助针

我们再离开一开始的标识符中的,该如何借助针呢

public class ThreadLockCAS { private volatile static int count = 0; public static void main(String[] args) { for (int i = 0; i < 10; i++) { Thread thread = new Thread(new Runnable() { @Override public void run() { for (int j = 0; j < 10000; j++) { count++; } } }); thread.start(); } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("count:" + count); }}1-5-1、借助思路

通过上对CAS的了解,我们就可以这么转换:

1、设置一个中的间最大值为02、第一个线程刚才的时候通过CAS将中的间最大值改名13、其他线程刚才 其后将要将0改名1的时候就则会惨败4、第一个线程业务转换完毕,其后通过cas将中的间最大值改名05、下一个线程其后通过CAS将0改名1就则会急于,给予针

1-5-2、借助一个CAS更为与转换的借助类1-5-2-1、借助加针的类

其中的UnsafeFactory类的标识符在纸片之前贴过,这个地方就暂时贴了。这个类中的主要就是cas()这个新方法。这就是CAS模式加针的转换。

State就是作为变动转换的最大值。

public class CASLock { private volatile int state; private static final Unsafe UNSAFE; private static final long OFFSET; static { try { UNSAFE= UnsafeFactory.getUnsafe(); OFFSET=UnsafeFactory.getFieldOffset(UNSAFE,CASLock.class,"state"); } catch (Exception e) { throw new Error(e); } } //设置CAS转换 public boolean cas(){ return UNSAFE.compareAndSwapInt(this,OFFSET,0,1); } public int getState() { return state; } public void setState(int state) { this.state = state; }}1-5-2-2、常用针借助

如下标识符,在第一个for反转中的创始人线程,然后线程中的借助了一个电磁场转换,这样第一个线程进到的针在此之后,其他线程都在透过电磁场转换,等第一个线程通过casLock.setState(0),拘禁针的时候,下一个线程casLock.getState()==0 && casLock.cas()就可以正式成立。

public class ThreadLockCAS { private volatile static int count = 0; static CASLock casLock = new CASLock(); public static void main(String[] args) { for (int i = 0; i < 10; i++) { Thread thread = new Thread(() -> { for (; ; ) { //state=0 if (casLock.getState() == 0 && casLock.cas()) { try { for (int j = 0; j < 10000; j++) { count++; } } finally { casLock.setState(0); } break; } } }); thread.start(); } try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(count); }}

虽然通过以上标识符借助了加针转换,由于常用了电磁场转换,这样下次的线程就则会直至空转,消耗CPU资源,毕竟这样得借助模式是不太友好的

1-6、CAS缺失

CAS 虽然高效地化解了水分子转换,但是还是普遍可知在一些缺失的,主要表现在三个上都:

电磁场 CAS 长时间地不急于,则则会给 CPU 造就非常大的开销只能确保一个共享表达式水分子转换ABA 原因1-6-1、ABA原因及化解方案

CAS搜索算法借助一个重要前提无需取出缓可知中的某时刻的统计数据,而在下时刻更为并代替,那么在这个时间差类则会避免统计数据的转变。

1-6-1-1、什么是ABA原因

人口为120人多个线程对一个水分子类透过转换的时候,某个线程在而会内将水分子类的最大值A重写为B,又马上将其重写为A,此时其他线程不感受,还是则会重写急于。

测试ABA原因

标识符运行流程:第一个线程要从1改名3,进到线程在此之后下次1秒。第二个线程将1改名2,然后又将2改名1第一个线程从1改名3,(因为原最大值还是1,这样就重写急于了)-其实此1非彼1

public class ABATest { public static void main(String[] args) { AtomicInteger atomicInteger = new AtomicInteger(1); new Thread(() -> { int value = atomicInteger.get(); System.out.println("Thread1 read value: " + value); // 阻塞1s LockSupport.parkNanos(1000000000L); // Thread1通过CAS重写value最大值为3 if (atomicInteger.compareAndSet(value, 3)) { System.out.println("Thread1 update from " + value + " to 3"); } else { System.out.println("Thread1 update fail!"); } }, "Thread1").start(); new Thread(() -> { int value = atomicInteger.get(); System.out.println("Thread2 read value: " + value); // Thread2通过CAS重写value最大值为2 if (atomicInteger.compareAndSet(value, 2)) { System.out.println("Thread2 update from " + value + " to 2"); // do something value = atomicInteger.get(); System.out.println("Thread2 read value: " + value); // Thread2通过CAS重写value最大值为1 if (atomicInteger.compareAndSet(value, 1)) { System.out.println("Thread2 update from " + value + " to 1"); } } }, "Thread2").start(); }}

运行结果如下:

Thread1不似乎Thread2对value的转换,误以为value=1不能重写过

1-6-1-2、ABA原因的化解方案

统计文档有个针称做急切针,是一种基于统计数据正式版借助统计数据同步的有助于,每次重写一次统计数据,正式版就则会透过累加。

AtomicStampedReference化解CAS的ABA原因

同样,Java也获取了确切来说的水分子所述类AtomicStampedReference

reference即我们仅仅传输的表达式,stamp是正式版,每次重写可以通过+1确保正式版所有基。这样就可以确保每次重写后的正式版也则会往上递增。

AtomicMarkableReference化解CAS的ABA原因

可以了解为纸片AtomicStampedReference的简化版,就是不体谅重写过几次,仅有仅有体谅前提重写过。因此表达式mark是boolean各种类型,仅有记录最大值前提有过重写。

常用AtomicStampedReference,依靠正式版号化解ABA原因public class AtomicStampedReferenceTest { public static void main(String[] args) { // 假设AtomicStampedReference Pair.reference最大值为1, Pair.stamp为1 AtomicStampedReference atomicStampedReference = new AtomicStampedReference(1,1); new Thread(()->{ int[] stampHolder = new int[1]; int value = (int) atomicStampedReference.get(stampHolder); int stamp = stampHolder[0]; System.out.println("Thread1 read value: " + value + ", stamp: " + stamp); // 阻塞1s LockSupport.parkNanos(1000000000L); // Thread1通过CAS重写value最大值为3 stamp是正式版,每次重写可以通过+1确保正式版所有基 if (atomicStampedReference.compareAndSet(value, 3,stamp,stamp+1)) { System.out.println("Thread1 update from " + value + " to 3"); } else { System.out.println("Thread1 update fail!"); } },"Thread1").start(); new Thread(()->{ int[] stampHolder = new int[1]; int value = (int)atomicStampedReference.get(stampHolder); int stamp = stampHolder[0]; System.out.println("Thread2 read value: " + value+ ", stamp: " + stamp); // Thread2通过CAS重写value最大值为2 if (atomicStampedReference.compareAndSet(value, 2,stamp,stamp+1)) { System.out.println("Thread2 update from " + value + " to 2"); // do something value = (int) atomicStampedReference.get(stampHolder); stamp = stampHolder[0]; System.out.println("Thread2 read value: " + value+ ", stamp: " + stamp); // Thread2通过CAS重写value最大值为1 if (atomicStampedReference.compareAndSet(value, 1,stamp,stamp+1)) { System.out.println("Thread2 update from " + value + " to 1"); } } },"Thread2").start(); }}

可执行结果

总结:

本篇文章主要描写CAS的加针的借助模式,以及通过常用AtomicStampedRefrence和AtomicMarkableReference化解ABA的原因

作者:Jony_zhang链接:

英特达泊西汀片(60mg)能治什么疾病
肠道菌群失调怎么调理
利活牌乳酸菌素片
水土不服拉肚子
胃不舒服怎么快速缓解
友情链接