当前位置: 首页 > news >正文

源码级别的讲解JAVA 中的CAS

在这里插入图片描述

没有CAS之前实现线程安全

  1. 多线程环境不使用原子类保证线程安全(基本数据类型)
public class T3
{volatile int number = 0;//读取public int getNumber(){return number;}//写入加锁保证原子性public synchronized void setNumber(){number++;}
}
  1. 多线程环境 使用原子类保证线程安全(基本数据类型)
public class T3
{AtomicInteger atomicInteger = new AtomicInteger();public int getAtomicInteger(){return atomicInteger.get();}public void setAtomicInteger(){atomicInteger.getAndIncrement();}
}

什么是CAS

compare and swap的缩写,中文翻译成比较并交换,实现并发算法时常用到的一种技术。它包含三个操作数——内存位置、预期原值及更新值。
执行CAS操作的时候,将内存位置的值与预期原值比较:如果相匹配,那么处理器会自动将该位置值更新为新值,如果不匹配,处理器不做任何操作,多个线程同时执行CAS操作只有一个会成功。

CAS (CompareAndSwap) 有3个操作数,位置内存值V,旧的预期值A,要修改的更新值B。当且仅当旧的预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做或重来。

硬件级别保证

CAS是JDK提供的非阻塞原子性操作,它通过硬件保证了比较-更新的原子性。
它是非阻塞的且自身原子性,也就是说这玩意效率更高且通过硬件保证,说明这玩意更可靠。

CAS是一条CPU的原子指令(cmpxchg指令),不会造成所谓的数据不一致问题,Unsafe提供的CAS方法(如compareAndSwapXXX)底层实现即为CPU指令cmpxchg。
执行cmpxchg指令的时候,会判断当前系统是否为多核系统,如果是就给总线加锁,只有一个线程会对总线加锁成功,加锁成功之后会执行cas操作,也就是说CAS的原子性实际上是CPU实现的, 其实在这一点上还是有排他锁的,只是比起用synchronized, 这里的排他时间要短的多, 所以在多线程情况下性能会比较好。

CASDemo代码

public class CASDemo
{public static void main(String[] args) throws InterruptedException{AtomicInteger atomicInteger = new AtomicInteger(5);System.out.println(atomicInteger.compareAndSet(5, 2020)+"\t"+atomicInteger.get());System.out.println(atomicInteger.compareAndSet(5, 1024)+"\t"+atomicInteger.get());}
}

源码分析compareAndSet(int expect,int update)

compareAndSet()方法的源代码:

image-20230209111536924

上面三个方法都是类似的,主要对4个参数做一下说明。
var1:表示要操作的对象
var2:表示要操作对象中属性地址的偏移量
var4:表示需要修改数据的期望的值
var5/var6:表示需要修改为的新值
源码分析compareAndSet(int expect,int update)

引出来一个问题:UnSafe类是什么?

CAS底层原理?如果知道,谈谈你对UnSafe的理解

UnSafe

image-20230209111714894

是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。Unsafe类存在于sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存,因为Java中CAS操作的执行依赖于Unsafe类的方法。

注意Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应任务

变量valueOffset,表示该变量值在内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数据的。

image-20230209111920068

变量value用volatile修饰,保证了多线程之间的内存可见性。

我们知道i++线程不安全的,那atomicInteger.getAndIncrement()

CAS的全称为Compare-And-Swap,它是一条CPU并发原语。
它的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的。
AtomicInteger 类主要利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。

CAS并发原语体现在JAVA语言中就是sun.misc.Unsafe类中的各个方法。调用UnSafe类中的CAS方法,JVM会帮我们实现出CAS汇编指令。这是一种完全依赖于硬件的功能,通过它实现了原子操作。再次强调,由于CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。

源码分析

OpenJDK源码里面查看下Unsafe.java

image-20230209112107852

假设线程A和线程B两个线程同时执行getAndAddInt操作(分别跑在不同CPU上):

1 AtomicInteger里面的value原始值为3,即主内存中AtomicInteger的value为3,根据JMM模型,线程A和线程B各自持有一份值为3的value的副本分别到各自的工作内存。

2 线程A通过getIntVolatile(var1, var2)拿到value值3,这时线程A被挂起。

3 线程B也通过getIntVolatile(var1, var2)方法获取到value值3,此时刚好线程B没有被挂起并执行compareAndSwapInt方法比较内存值也为3,成功修改内存值为4,线程B打完收工,一切OK。

4 这时线程A恢复,执行compareAndSwapInt方法比较,发现自己手里的值数字3和主内存的值数字4不一致,说明该值已经被其它线程抢先一步修改过了,那A线程本次修改失败,只能重新读取重新来一遍了。

5 线程A重新获取value值,因为变量value被volatile修饰,所以其它线程对它的修改,线程A总是能够看到,线程A继续执行compareAndSwapInt进行比较替换,直到成功。

总结

你只需要记住:CAS是靠硬件实现的从而在硬件层面提升效率,最底层还是交给硬件来保证原子性和可见性
实现方式是基于硬件平台的汇编指令,在intel的CPU中(X86机器上),使用的是汇编指令cmpxchg指令。

核心思想就是:比较要更新变量的值V和预期值E(compare),相等才会将V的值设为新值N(swap)如果不相等自旋再来。

原子引用

image-20230209143121180

Java中提供了几类原子操作类,通过自旋+CAS来解决线程不安全问题。

基本类型原子类 -AtomicLong、AtomicInteger、AtomicBoolean

这些类实现了++,–,+delta的原子操作。

常用API简介

  • public final int get() //获取当前的值

  • public final int getAndSet(int newValue)//获取当前的值,并设置新的值

  • public final int getAndIncrement()//获取当前的值,并自增

  • public final int getAndDecrement() //获取当前的值,并自减

  • public final int getAndAdd(int delta) //获取当前的值,并加上预期的值

  • boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将该值设置为输入值(update)

    class MyNumber
    {@Getterprivate AtomicInteger atomicInteger = new AtomicInteger();public void addPlusPlus(){atomicInteger.incrementAndGet();}
    }public class AtomicIntegerDemo
    {public static void main(String[] args) throws InterruptedException{MyNumber myNumber = new MyNumber();CountDownLatch countDownLatch = new CountDownLatch(100);for (int i = 1; i <=100; i++) {new Thread(() -> {try{for (int j = 1; j <=5000; j++){myNumber.addPlusPlus();}}finally {countDownLatch.countDown();}},String.valueOf(i)).start();}countDownLatch.await();System.out.println(myNumber.getAtomicInteger().get());}
    }
    

数组类型原子类- AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray

这个就是数组类型,和单独的对象操作基本一致,只不过在设置的时候需要填入下标罢了。

public class AtomicIntegerArrayDemo
{public static void main(String[] args){AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(new int[5]);//AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(5);//AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(new int[]{1,2,3,4,5});for (int i = 0; i <atomicIntegerArray.length(); i++) {System.out.println(atomicIntegerArray.get(i));}System.out.println();System.out.println();System.out.println();int tmpInt = 0;tmpInt = atomicIntegerArray.getAndSet(0,1122);System.out.println(tmpInt+"\t"+atomicIntegerArray.get(0));atomicIntegerArray.getAndIncrement(1);atomicIntegerArray.getAndIncrement(1);tmpInt = atomicIntegerArray.getAndIncrement(1);System.out.println(tmpInt+"\t"+atomicIntegerArray.get(1));}
}

引用类型原子类- AtomicReference、AtomicStampedReference、AtomicMarkableReference

  • AtomicReference
@Getter
@ToString
@AllArgsConstructor
class User
{String userName;int    age;
}public class AtomicReferenceDemo
{public static void main(String[] args){User z3 = new User("z3",24);User li4 = new User("li4",26);AtomicReference<User> atomicReferenceUser = new AtomicReference<>();atomicReferenceUser.set(z3);System.out.println(atomicReferenceUser.compareAndSet(z3,li4)+"\t"+atomicReferenceUser.get().toString());System.out.println(atomicReferenceUser.compareAndSet(z3,li4)+"\t"+atomicReferenceUser.get().toString());}
}
  • AtomicStampedReference 携带版本号的引用类型原子类,可以解决ABA问题

    public class ABADemo
    {static AtomicInteger atomicInteger = new AtomicInteger(100);static AtomicStampedReference atomicStampedReference = new AtomicStampedReference(100,1);public static void main(String[] args){abaProblem();abaResolve();}public static void abaResolve(){new Thread(() -> {int stamp = atomicStampedReference.getStamp();System.out.println("t3 ----第1次stamp  "+stamp);try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }atomicStampedReference.compareAndSet(100,101,stamp,stamp+1);System.out.println("t3 ----第2次stamp  "+atomicStampedReference.getStamp());atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);System.out.println("t3 ----第3次stamp  "+atomicStampedReference.getStamp());},"t3").start();new Thread(() -> {int stamp = atomicStampedReference.getStamp();System.out.println("t4 ----第1次stamp  "+stamp);//暂停几秒钟线程try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }boolean result = atomicStampedReference.compareAndSet(100, 20210308, stamp, stamp + 1);System.out.println(Thread.currentThread().getName()+"\t"+result+"\t"+atomicStampedReference.getReference());},"t4").start();}public static void abaProblem(){new Thread(() -> {atomicInteger.compareAndSet(100,101);atomicInteger.compareAndSet(101,100);},"t1").start();try { TimeUnit.MILLISECONDS.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); }new Thread(() -> {atomicInteger.compareAndSet(100,20210308);System.out.println(atomicInteger.get());},"t2").start();}
    }
    
  • AtomicMarkableReference 原子更新带有标记位的引用类型对象

public class ABADemo
{static AtomicInteger atomicInteger = new AtomicInteger(100);static AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<>(100,1);static AtomicMarkableReference<Integer> markableReference = new AtomicMarkableReference<>(100,false);public static void main(String[] args){new Thread(() -> {atomicInteger.compareAndSet(100,101);atomicInteger.compareAndSet(101,100);System.out.println(Thread.currentThread().getName()+"\t"+"update ok");},"t1").start();new Thread(() -> {//暂停几秒钟线程try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }atomicInteger.compareAndSet(100,2020);},"t2").start();//暂停几秒钟线程try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }System.out.println(atomicInteger.get());System.out.println();System.out.println();System.out.println();System.out.println("============以下是ABA问题的解决,让我们知道引用变量中途被更改了几次=========================");new Thread(() -> {System.out.println(Thread.currentThread().getName()+"\t 1次版本号"+stampedReference.getStamp());//故意暂停200毫秒,让后面的t4线程拿到和t3一样的版本号try { TimeUnit.MILLISECONDS.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); }stampedReference.compareAndSet(100,101,stampedReference.getStamp(),stampedReference.getStamp()+1);System.out.println(Thread.currentThread().getName()+"\t 2次版本号"+stampedReference.getStamp());stampedReference.compareAndSet(101,100,stampedReference.getStamp(),stampedReference.getStamp()+1);System.out.println(Thread.currentThread().getName()+"\t 3次版本号"+stampedReference.getStamp());},"t3").start();new Thread(() -> {int stamp = stampedReference.getStamp();System.out.println(Thread.currentThread().getName()+"\t =======1次版本号"+stamp);//暂停2秒钟,让t3先完成ABA操作了,看看自己还能否修改try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }boolean b = stampedReference.compareAndSet(100, 2020, stamp, stamp + 1);System.out.println(Thread.currentThread().getName()+"\t=======2次版本号"+stampedReference.getStamp()+"\t"+stampedReference.getReference());},"t4").start();System.out.println();System.out.println();System.out.println();System.out.println("============AtomicMarkableReference不关心引用变量更改过几次,只关心是否更改过======================");new Thread(() -> {boolean marked = markableReference.isMarked();System.out.println(Thread.currentThread().getName()+"\t 1次版本号"+marked);try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }markableReference.compareAndSet(100,101,marked,!marked);System.out.println(Thread.currentThread().getName()+"\t 2次版本号"+markableReference.isMarked());markableReference.compareAndSet(101,100,markableReference.isMarked(),!markableReference.isMarked());System.out.println(Thread.currentThread().getName()+"\t 3次版本号"+markableReference.isMarked());},"t5").start();new Thread(() -> {boolean marked = markableReference.isMarked();System.out.println(Thread.currentThread().getName()+"\t 1次版本号"+marked);//暂停几秒钟线程try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }markableReference.compareAndSet(100,2020,marked,!marked);System.out.println(Thread.currentThread().getName()+"\t"+markableReference.getReference()+"\t"+markableReference.isMarked());},"t6").start();}
}

原子操作增强类原理深度解析

  • DoubleAccumulator
  • DoubleAdder
  • LongAccumulator
  • LongAdder

阿里要命题目

image-20230209135916464

常用API

常用API

LongAdder只能用来计算加法,且从零开始计算

LongAccumulator提供了自定义的函数操作

Demo

public class LongAdderAPIDemo
{public static void main(String[] args){LongAdder longAdder = new LongAdder();longAdder.increment();longAdder.increment();longAdder.increment();System.out.println(longAdder.longValue());LongAccumulator longAccumulator = new LongAccumulator((x,y) -> x * y,2);longAccumulator.accumulate(1);longAccumulator.accumulate(2);longAccumulator.accumulate(3);System.out.println(longAccumulator.longValue());}
}

LongAdder高性能对比Code演示

class ClickNumberNet
{int number = 0;public synchronized void clickBySync(){number++;}AtomicLong atomicLong = new AtomicLong(0);public void clickByAtomicLong(){atomicLong.incrementAndGet();}LongAdder longAdder = new LongAdder();public void clickByLongAdder(){longAdder.increment();}LongAccumulator longAccumulator = new LongAccumulator((x,y) -> x + y,0);public void clickByLongAccumulator(){longAccumulator.accumulate(1);}
}public class LongAdderDemo2
{public static void main(String[] args) throws InterruptedException{ClickNumberNet clickNumberNet = new ClickNumberNet();long startTime;long endTime;CountDownLatch countDownLatch = new CountDownLatch(50);CountDownLatch countDownLatch2 = new CountDownLatch(50);CountDownLatch countDownLatch3 = new CountDownLatch(50);CountDownLatch countDownLatch4 = new CountDownLatch(50);startTime = System.currentTimeMillis();for (int i = 1; i <=50; i++) {new Thread(() -> {try{for (int j = 1; j <=100 * 10000; j++) {clickNumberNet.clickBySync();}}finally {countDownLatch.countDown();}},String.valueOf(i)).start();}countDownLatch.await();endTime = System.currentTimeMillis();System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t clickBySync result: "+clickNumberNet.number);startTime = System.currentTimeMillis();for (int i = 1; i <=50; i++) {new Thread(() -> {try{for (int j = 1; j <=100 * 10000; j++) {clickNumberNet.clickByAtomicLong();}}finally {countDownLatch2.countDown();}},String.valueOf(i)).start();}countDownLatch2.await();endTime = System.currentTimeMillis();System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t clickByAtomicLong result: "+clickNumberNet.atomicLong);startTime = System.currentTimeMillis();for (int i = 1; i <=50; i++) {new Thread(() -> {try{for (int j = 1; j <=100 * 10000; j++) {clickNumberNet.clickByLongAdder();}}finally {countDownLatch3.countDown();}},String.valueOf(i)).start();}countDownLatch3.await();endTime = System.currentTimeMillis();System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t clickByLongAdder result: "+clickNumberNet.longAdder.sum());startTime = System.currentTimeMillis();for (int i = 1; i <=50; i++) {new Thread(() -> {try{for (int j = 1; j <=100 * 10000; j++) {clickNumberNet.clickByLongAccumulator();}}finally {countDownLatch4.countDown();}},String.valueOf(i)).start();}countDownLatch4.await();endTime = System.currentTimeMillis();System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t clickByLongAccumulator result: "+clickNumberNet.longAccumulator.longValue());}
}

image-20230209140847813

源码、原理分析

架构

架构

LongAdder是Striped64的子类

image-20230209140942978

image-20230209140953665

原理(LongAdder为什么这么快)

image-20230209141026855

image-20230209135916464

LongAdder是Striped64的子类,Striped64有几个比较重要的成员函数

  
/** Number of CPUS, to place bound on table size        CPU数量,即cells数组的最大长度 */
static final int NCPU = Runtime.getRuntime().availableProcessors();/*** Table of cells. When non-null, size is a power of 2.
cells数组,为2的幂,2,4,8,16.....,方便以后位运算*/
transient volatile Cell[] cells;/**基础value值,当并发较低时,只累加该值主要用于没有竞争的情况,通过CAS更新。* Base value, used mainly when there is no contention, but also as* a fallback during table initialization races. Updated via CAS.*/
transient volatile long base;/**创建或者扩容Cells数组时使用的自旋锁变量调整单元格大小(扩容),创建单元格时使用的锁。* Spinlock (locked via CAS) used when resizing and/or creating Cells. */
transient volatile int cellsBusy;

Cell - 是 java.util.concurrent.atomic 下 Striped64 的一个内部类

image-20230209141256833

LongAdder为什么这么快

LongAdder的基本思路就是分散热点,将value值分散到一个Cell数组中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中的那个值进行CAS操作,这样热点就被分散了,冲突的概率就小很多。如果要获取真正的long值,只要将各个槽中的变量值累加返回。

内部有一个base变量,一个Cell[]数组。sum()会将所有Cell数组中的value和base累加作为返回值,核心的思想就是将之前AtomicLong一个value的更新压力分散到多个value中去,
从而降级更新热点。

image-20230209141351297

LongAdder在无竞争的情况,跟AtomicLong一样,对同一个base进行操作,当出现竞争关系时则是采用化整为零的做法,从空间换时间,用一个数组cells,将一个value拆分进这个数组cells。多个线程需要同时对value进行操作时候,可以对线程id进行hash得到hash值,再根据hash值映射到这个数组cells的某个下标,再对该下标所对应的值进行自增操作。当所有线程操作完毕,将数组cells的所有值和无竞争值base都加起来作为最终结果。

image-20230209141523837

总结

  • AtomicLong
    • 原理:
      • CAS+自旋
      • incrementAndGet
    • 场景:
      • 低并发下的全局计算
      • AtomicLong能保证并发情况下计数的准确性,其内部通过CAS来解决并发安全性的问题。
    • 缺陷:
      • 高并发后性能急剧下降
      • AtomicLong的自旋会成为瓶颈: N个线程CAS操作修改线程的值,每次只有一个成功过,其它N - 1失败,失败的不停的自旋直到成功,这样大量失败自旋的情况,一下子cpu就打高了。
  • LongAdder
    • 原理:
      • CAS+Base+Cell数组分散
      • 空间换时间并分散了热点数据
    • 场景:
      • 高并发下的全局计算
    • 缺陷:
      • sum求和后还有计算线程修改结果的话,最后结果不够准确

自旋锁,借鉴CAS思想

自旋锁(spinlock)

是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,
当线程发现锁被占用时,会不断循环判断锁的状态,直到获取。这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU

OpenJDK源码里面查看下Unsafe.java

/*** 题目:实现一个自旋锁* 自旋锁好处:循环比较获取没有类似wait的阻塞。** 通过CAS操作完成自旋锁,A线程先进来调用myLock方法自己持有锁5秒钟,B随后进来后发现* 当前有线程持有锁,不是null,所以只能通过自旋等待,直到A释放锁后B随后抢到。*/
public class SpinLockDemo
{AtomicReference<Thread> atomicReference = new AtomicReference<>();public void myLock(){Thread thread = Thread.currentThread();System.out.println(Thread.currentThread().getName()+"\t come in");while(!atomicReference.compareAndSet(null,thread)){}}public void myUnLock(){Thread thread = Thread.currentThread();atomicReference.compareAndSet(thread,null);System.out.println(Thread.currentThread().getName()+"\t myUnLock over");}public static void main(String[] args){SpinLockDemo spinLockDemo = new SpinLockDemo();new Thread(() -> {spinLockDemo.myLock();try { TimeUnit.SECONDS.sleep( 5 ); } catch (InterruptedException e) { e.printStackTrace(); }spinLockDemo.myUnLock();},"A").start();//暂停一会儿线程,保证A线程先于B线程启动并完成try { TimeUnit.SECONDS.sleep( 1 ); } catch (InterruptedException e) { e.printStackTrace(); }new Thread(() -> {spinLockDemo.myLock();spinLockDemo.myUnLock();},"B").start();}
}

CAS缺点

循环时间长开销很大

我们可以看到getAndAddInt方法执行时,有个do while

image-20230209113213282

如果CAS失败,会一直进行尝试。如果CAS长时间一直不成功,可能会给CPU带来很大的开销。

引出来ABA问题

ABA问题怎么产生的

CAS算法实现一个重要前提需要取出内存中某时刻的数据并在当下时刻比较并替换,那么在这个时间差类会导致数据的变化。

比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且线程two进行了一些操作将值变成了B,
然后线程two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后线程one操作成功。

尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的。

解决方案-版本号时间戳原子引用

AtomicStampedReference

AtomicStampedReference在构建的时候需要一个类似于版本号的int类型变量stamped,每一次针对共享数据的变化都会导致该 stamped 的变化(stamped 需要应用程序自身去负责,AtomicStampedReference并不提供,一般使用时间戳作为版本号),因此就可以避免ABA问题的出现,AtomicStampedReference的使用也是极其简单的,创建时我们不仅需要指定初始值,还需要设定stamped的初始值,在AtomicStampedReference的内部会将这两个变量封装成Pair对象,代码如下所示。

public class ABADemo
{static AtomicInteger atomicInteger = new AtomicInteger(100);static AtomicStampedReference atomicStampedReference = new AtomicStampedReference(100,1);public static void main(String[] args){new Thread(() -> {atomicInteger.compareAndSet(100,101);atomicInteger.compareAndSet(101,100);},"t1").start();new Thread(() -> {//暂停一会儿线程try { Thread.sleep( 500 ); } catch (InterruptedException e) { e.printStackTrace(); };            System.out.println(atomicInteger.compareAndSet(100, 2019)+"\t"+atomicInteger.get());},"t2").start();//暂停一会儿线程,main彻底等待上面的ABA出现演示完成。try { Thread.sleep( 2000 ); } catch (InterruptedException e) { e.printStackTrace(); }System.out.println("============以下是ABA问题的解决=============================");new Thread(() -> {int stamp = atomicStampedReference.getStamp();System.out.println(Thread.currentThread().getName()+"\t 首次版本号:"+stamp);//1//暂停一会儿线程,try { Thread.sleep( 1000 ); } catch (InterruptedException e) { e.printStackTrace(); }atomicStampedReference.compareAndSet(100,101,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);System.out.println(Thread.currentThread().getName()+"\t 2次版本号:"+atomicStampedReference.getStamp());atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);System.out.println(Thread.currentThread().getName()+"\t 3次版本号:"+atomicStampedReference.getStamp());},"t3").start();new Thread(() -> {int stamp = atomicStampedReference.getStamp();System.out.println(Thread.currentThread().getName()+"\t 首次版本号:"+stamp);//1//暂停一会儿线程,获得初始值100和初始版本号1,故意暂停3秒钟让t3线程完成一次ABA操作产生问题try { Thread.sleep( 3000 ); } catch (InterruptedException e) { e.printStackTrace(); }boolean result = atomicStampedReference.compareAndSet(100,2019,stamp,stamp+1);System.out.println(Thread.currentThread().getName()+"\t"+result+"\t"+atomicStampedReference.getReference());},"t4").start();}
}

相关文章:

源码级别的讲解JAVA 中的CAS

没有CAS之前实现线程安全 多线程环境不使用原子类保证线程安全&#xff08;基本数据类型&#xff09; public class T3 {volatile int number 0;//读取public int getNumber(){return number;}//写入加锁保证原子性public synchronized void setNumber(){number;} }多线程环…...

JUC锁与AQS技术【我的Android开发技术】

JUC锁与AQS技术【我的Android开发技术】 AQS原理 AQS就是一个同步器&#xff0c;要做的事情就相当于一个锁&#xff0c;所以就会有两个动作&#xff1a;一个是获取&#xff0c;一个是释放。获取释放的时候该有一个东西来记住他是被用还是没被用&#xff0c;这个东西就是一个状…...

【问题代码】顺序点的深入理解(汇编剖析+手画图解)

这好像是一个哲学问题。 目录 前言 一、顺序点是什么&#xff1f; 二、发生有关顺序点的问题代码 vs中&#xff1a; gcc中&#xff1a; 三、细读汇编 1.vs汇编如下&#xff08;示例&#xff09;&#xff1a; 2.gcc汇编如下&#xff08;示例&#xff09;&#xff1a; 四…...

BinaryAI全新代码匹配模型BAI-2.0上线,“大模型”时代的安全实践

导语BinaryAI&#xff08;https://www.binaryai.net&#xff09;科恩实验室在2021年8月首次发布二进制安全智能分析平台—BinaryAI&#xff0c;BinaryAI可精准高效识别二进制文件的第三方组件及其版本号&#xff0c;旨在推动SCA&#xff08;Software Composition Analysis&…...

nvidia设置wifi和接口

tx-nx设置wifi和接口前言基础知识点1.创建和删除一个wifi连接2. 启动连接和关闭连接代码和调试1. 代码展示2. 调试写到最后前言 针对嵌入式开发&#xff0c;有时候通过QT或PAD跨网络对设备设置WIFI&#xff0c;在此记录下&#xff0c;方便后续的查阅。 基础知识点 1.创建和删…...

PostgreSQL 变化数据捕捉(CDC)

PostgreSQL 变化数据捕捉&#xff08;CDC&#xff09;基于CDC&#xff08;变更数据捕捉&#xff09;的增量数据集成总体步骤&#xff1a;1.捕获源数据库中的更改数据2.将变更的数据转换为您的消费者可以接受的格式3.将数据发布到消费者或目标数据库PostgreSQL支持触发器&#x…...

Spring 事务【隔离级别与传播机制】

Spring 事务【隔离级别与传播机制】&#x1f34e;一.事务隔离级别&#x1f352;1.1 事务特性回顾&#x1f352;1.2 事务的隔离级别(5种)&#x1f352;1.3 事务隔离级别的设置&#x1f34e;二.Spring 事务传播机制&#x1f352;2.1 Spring 事务传播机制的作用&#x1f352;2.2 事…...

HTTP和HTTPS协议

HTTP协议 HTTP协议是一种应用层的协议&#xff0c;全称为超文本传输协议。 URL URL值统一资源定位标志&#xff0c;也就是俗称的网址。 协议方案名 http://表示的就是协议方案名&#xff0c;常用的协议有HTTP协议、HTTPS协议、FTP协议等。HTTPS协议是以HTTP协议为基础&#…...

day3——有关java运算符的笔记

今天主要学习的内容有java的运算符 赋值运算符算数运算符关系运算符逻辑运算符位运算符&#xff08;专门写一篇笔记&#xff09;条件运算符运算符的优先级流程控制 赋值运算符 赋值运算符&#xff08;&#xff09;主要用于给变量赋值&#xff0c;可以跟算数运算符相结合&…...

Git多人协同远程开发

1. 李四&#xff08;项目负责人&#xff09;操作步骤 在github中创建远程版本库testgit将基础代码上传⾄testgit远程库远程库中基于main分⽀创建dev分⽀将 githubleaflife/testgit 共享给组员李四继续在基础代码上添加⾃⼰负责的模块内容 2. 张三、王五&#xff08;组员&…...

Chapter4:机器人仿真

ROS1{\rm ROS1}ROS1的基础及应用&#xff0c;基于古月的课&#xff0c;各位可以去看&#xff0c;基于hawkbot{\rm hawkbot}hawkbot机器人进行实际操作。 ROS{\rm ROS}ROS版本&#xff1a;ROS1{\rm ROS1}ROS1的Melodic{\rm Melodic}Melodic&#xff1b;实际机器人&#xff1a;Ha…...

python(14)--集合

前言 本篇文章学习的是 python 中集合的基础知识。 集合元素的内容是不可变的&#xff0c;常见的元素有整数、浮点数、字符串、元组等。至于可变内容列表、字典、集合等不可以是集合元素。虽然集合不可以是集合的元素&#xff0c;但是集合本身是可变的&#xff0c;可以去增加或…...

【Spark分布式内存计算框架——Spark Core】4. RDD函数(中)Transformation函数、Action函数

3.2 Transformation函数 在Spark中Transformation操作表示将一个RDD通过一系列操作变为另一个RDD的过程&#xff0c;这个操作可能是简单的加减操作&#xff0c;也可能是某个函数或某一系列函数。值得注意的是Transformation操作并不会触发真正的计算&#xff0c;只会建立RDD间…...

Mysql 数据类型

1、数值数据类型 1.1 整数类型(精确值) INTEGER, INT, SMALLINT, TINYINT, MEDIUMINT, BIGINT MySQL支持SQL标准的整数类型INTEGER (或INT)和SMALLINT。作为标准的扩展&#xff0c;MySQL还支持整数类型TINYINT、MEDIUMINT和BIGINT。下表显示了每种整数类型所需的存储和范围。…...

运行Whisper笔记(1)

最近chatGPT很火&#xff0c;就去逛了一下openai的github项目。发现了这个项目。 这个项目可以识别视频中的音频&#xff0c;转换出字幕。 带着一颗好奇的心就尝试自己去部署玩一玩 跟着这篇文章一步步来进行安装&#xff0c;并且跟着这篇文章解决途中遇到的问题。 途中还会遇…...

2023年最强大的12款数据可视化工具,值得收藏

做数据分析也有年头了&#xff0c;好的坏的工具都用过&#xff0c;推荐几个觉得很好用的&#xff0c;避坑必看&#xff01; PS&#xff1a;一般比较成熟的公司里&#xff0c;数据分析工具不只是满足业务分析和报表制作&#xff0c;像我现在给我们公司选型BI工具&#xff0c;是做…...

LeetCode刷题系列 -- 523. 连续的子数组和

给你一个整数数组 nums 和一个整数 k &#xff0c;编写一个函数来判断该数组是否含有同时满足下述条件的连续子数组&#xff1a;子数组大小 至少为 2 &#xff0c;且子数组元素总和为 k 的倍数。如果存在&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。如果存…...

LeetCode刷题系列 -- 525. 连续数组

给定一个二进制数组 nums , 找到含有相同数量的 0 和 1 的最长连续子数组&#xff0c;并返回该子数组的长度。示例 1:输入: nums [0,1]输出: 2说明: [0, 1] 是具有相同数量 0 和 1 的最长连续子数组。示例 2:输入: nums [0,1,0]输出: 2说明: [0, 1] (或 [1, 0]) 是具有相同数…...

JavaEE15-Spring Boot统一功能处理

目录 1.统一用户登录权限效验 1.1.最初用户登录验证 1.2.Spring AOP用户统一登录验证的问题 1.3.Spring拦截器 1.3.1.创建自定义拦截器&#xff0c;实现 HandlerInterceptor 接口并重写 preHandle&#xff08;执行具体方法之前的预处理&#xff09;方法 1.3.2.将自定义拦…...

centos7.6 设置防火墙

1、查看系统版本 cat /etc/redhat-release2、查看防火墙运行状态 systemctl status firewalld这里可以看到当前是未运行状态(inactive)。 3、关闭开机自启动防火墙 systemctl disable firewalld.service4、启动防火墙并查看状态&#xff0c;系统默认 22 端口是开启的。 sy…...

在线支付系列【22】微信支付实战篇之集成服务商API

有道无术&#xff0c;术尚可求&#xff0c;有术无道&#xff0c;止于术。 文章目录前言1. 环境搭建2. 特约商户进件3. 统一下单总结前言 在上篇文档中&#xff0c;我们做好了接入前准备工作&#xff0c;接下来使用开源框架集成服务商相关API。 一个简单的支付系统完成支付流程…...

3.2 埃尔米特转置

定义 对于复矩阵&#xff0c;转置又不一样&#xff0c;常见的操作是共轭转置&#xff0c;也叫埃尔米特转置Hermitian transpose。埃尔米特转置就是对矩阵先共轭&#xff0c;再转置&#xff0c;一般来说用三种符号表示埃尔米特转置&#xff1a; 第一种符号是AHA^HAH&#xff0c…...

Python爬虫之Scrapy框架系列(13)——实战ZH小说爬取数据入MySql数据库

目录&#xff1a;1 数据持久化存储&#xff0c;写入Mysql数据库①定义结构化字段&#xff1a;②重新编写爬虫文件&#xff1a;③编写管道文件&#xff1a;④辅助配置&#xff08;修改settings.py文件&#xff09;&#xff1a;⑤navicat创库建表&#xff1a;⑥ 效果如下&#xf…...

MySQL篇02-三大范式,多表查询

数据入库时,由于数据设计不合理&#xff0c;会存在数据重复、更新插入异常等情况, 故数据库中表的设计遵循的设计规范&#xff1a;三大范式1.第一范式(1NF)要求数据库的每一列都是不可分割的原子数据项&#xff0c;即原子性。强调的是列的原子性&#xff0c;即数据库中每一列的…...

vue-cli3创建Vue项目

文章目录前言一、使用vue-cli3创建项目1.检查当前vue的版本2.下载并安装Vue-cli33.使用命令行创建项目二、关于配置前言 本文讲解了如何使用vue-cli3创建属于自己的Vue项目&#xff0c;如果本文对你有所帮助请三连支持博主&#xff0c;你的支持是我更新的动力。 下面案例可供…...

Linux perf probe 的使用(三)

文章目录前言一、Dynamic Tracing二、kprobes2.1 perf kprobe 的使用2.2 kprobe Arguments3.3 tcp_sendmsg()3.3.1 Kernel: tcp_sendmsg()3.3.2 Kernel: tcp_sendmsg() with size3.3.2 Kernel: tcp_sendmsg() line number and local variable三、uprobes的使用3.1 perf uprobe …...

python GUI编程 多窗口跳转

# 多窗口跳转例子from tkinter import *def main(): # 主窗体def goto(num):root.destroy() # 关闭主窗体if num 1:one() # 进入第1个窗体elif num 2:two() # 进入第2个窗体root Tk()root.geometry(300x150600200)root.title(登录窗口)but1 Button(root, text"进入…...

nuxt 学习笔记

这里写目录标题路由跳转NuxtLinkquery参数params参数嵌套路由tab切换效果layouts 文件夹强制约定放置所有布局文件&#xff0c;并以插槽的形式作用在页面中1.在app.vue里面2.component 组件使用Vue < component :is"">Vuex生命周期数据请求useFetchuseAsyncDat…...

Python编程自动化办公案例(1)

作者简介&#xff1a;一名在校计算机学生、每天分享Python的学习经验、和学习笔记。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​​ 目录 前言 一.使用库讲解 1.xlrd 2.xlwt 二.主要案例 1.批量合并 模板如下&#xf…...

一站式 Elasticsearch 集群指标监控与运维管控平台

上篇文章写了一下消息运维管理平台&#xff0c;今天带来的是ES的监控和运维平台。目前初创企业&#xff0c;不像大型互联网公司&#xff0c;可以重复的造轮子。前期还是快速迭代试错阶段&#xff0c;方便拿到市场反馈&#xff0c;及时调整自己的战略和产品方向。让自己活下去&a…...

网站域名用公司注册信息查询/公司企业网站制作需要多少钱

在C语言和C中&#xff0c;经常会出现某一种数据类型所占的字节数为多大的问题&#xff0c;其实这些机器字长和编译器有关系&#xff0c;通过总结&#xff0c;如下图&#xff1a; 其中&#xff0c;彩色部分代表的意思是该类型在三种平台下所占字节大小不同&#xff0c;黑色说明…...

做网站 用哪个网盘好/百家号官网

测试环境&#xff1a;Windows XP ProfessionMySQL 5.0.45Oracle 9iDB2 UDB 9.1测试的SQL脚本如下&#xff1a;此脚本适合MySQL、DB2&#xff0c;如果要在Oracle上执行&#xff0c;需要做个替换BIGINTINTEGER、VARCHARVARCHAR2。CREATETABLECUSTOMERS (ID BIGINT NOTNULL,NAME V…...

javaweb是用java做网站吗/公关公司

1、同步和异步的区别 当未使用异步页时,一个线程只能为同一个页面的请求服务. 即使页面请求过程中处理其它的I/O等操作时,此线程也一直处于等待状态. 当此页面使用完此线程时,才将它放回到线程池. 线程数量是有限的! 所以当不使用线程时及时放回线池可以使系统性能大大提高! 当…...

中国建设信用卡积分兑换网站/站长查询域名

最近做图像分类&#xff0c;数据集的整理真的好费时间&#xff0c;找到一个代码&#xff0c;非常好使&#xff0c;推荐给大家。实现的功能是&#xff0c;将一个文件夹下的每个子文件夹的一半数据分割出来并保存&#xff0c;生成相应的train.txt,test.txt。自己弄数据集&#xf…...

青岛市北区网站制作公司/什么叫seo优化

最近在安装Xcode的插件时&#xff0c;本来应该选择loadbundle&#xff0c;但是选择了skip bundle&#xff0c;导致插件一直安装不上&#xff0c;后来网上找了一堆东西&#xff0c;但是好像没用。幸好&#xff0c;最后解决了。 先说说原因&#xff1a;这是因为Xcode黑名单的问题…...

网站开发者的设计构想/品牌宣传文案范文

1.Linux线程概念 1.1.线程的概念 书本中对线程的描述&#xff1a; 1.在进程内部运行的执行流 2.线程比进程粒度更细&#xff0c;调度成本更低 3.线程是CPU调度的基本单位 注&#xff1a;上面的说法都没有问题&#xff0c;这里我们以Linux内核的角度切入来讲解线程。 线程的概念…...