源码级别的讲解JAVA 中的CAS

没有CAS之前实现线程安全
- 多线程环境不使用原子类保证线程安全(基本数据类型)
public class T3
{volatile int number = 0;//读取public int getNumber(){return number;}//写入加锁保证原子性public synchronized void setNumber(){number++;}
}
- 多线程环境 使用原子类保证线程安全(基本数据类型)
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()方法的源代码:

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

引出来一个问题:UnSafe类是什么?
CAS底层原理?如果知道,谈谈你对UnSafe的理解
UnSafe

是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。Unsafe类存在于sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存,因为Java中CAS操作的执行依赖于Unsafe类的方法。
注意Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应任务
变量valueOffset,表示该变量值在内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数据的。

变量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

假设线程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)如果不相等自旋再来。
原子引用

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
阿里要命题目

常用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());}
}

源码、原理分析
架构

LongAdder是Striped64的子类


原理(LongAdder为什么这么快)


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 的一个内部类

LongAdder为什么这么快
LongAdder的基本思路就是分散热点,将value值分散到一个Cell数组中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中的那个值进行CAS操作,这样热点就被分散了,冲突的概率就小很多。如果要获取真正的long值,只要将各个槽中的变量值累加返回。
内部有一个base变量,一个Cell[]数组。sum()会将所有Cell数组中的value和base累加作为返回值,核心的思想就是将之前AtomicLong一个value的更新压力分散到多个value中去,
从而降级更新热点。

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

总结
- AtomicLong
- 原理:
- CAS+自旋
- incrementAndGet
- 场景:
- 低并发下的全局计算
- AtomicLong能保证并发情况下计数的准确性,其内部通过CAS来解决并发安全性的问题。
- 缺陷:
- 高并发后性能急剧下降
- AtomicLong的自旋会成为瓶颈: N个线程CAS操作修改线程的值,每次只有一个成功过,其它N - 1失败,失败的不停的自旋直到成功,这样大量失败自旋的情况,一下子cpu就打高了。
- 原理:
- LongAdder
- 原理:
- CAS+Base+Cell数组分散
- 空间换时间并分散了热点数据
- 场景:
- 高并发下的全局计算
- 缺陷:
- sum求和后还有计算线程修改结果的话,最后结果不够准确
- 原理:
自旋锁,借鉴CAS思想
自旋锁(spinlock)
是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,
当线程发现锁被占用时,会不断循环判断锁的状态,直到获取。这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU

/*** 题目:实现一个自旋锁* 自旋锁好处:循环比较获取没有类似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

如果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之前实现线程安全 多线程环境不使用原子类保证线程安全(基本数据类型) 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就是一个同步器,要做的事情就相当于一个锁,所以就会有两个动作:一个是获取,一个是释放。获取释放的时候该有一个东西来记住他是被用还是没被用,这个东西就是一个状…...
【问题代码】顺序点的深入理解(汇编剖析+手画图解)
这好像是一个哲学问题。 目录 前言 一、顺序点是什么? 二、发生有关顺序点的问题代码 vs中: gcc中: 三、细读汇编 1.vs汇编如下(示例): 2.gcc汇编如下(示例): 四…...
BinaryAI全新代码匹配模型BAI-2.0上线,“大模型”时代的安全实践
导语BinaryAI(https://www.binaryai.net)科恩实验室在2021年8月首次发布二进制安全智能分析平台—BinaryAI,BinaryAI可精准高效识别二进制文件的第三方组件及其版本号,旨在推动SCA(Software Composition Analysis&…...
nvidia设置wifi和接口
tx-nx设置wifi和接口前言基础知识点1.创建和删除一个wifi连接2. 启动连接和关闭连接代码和调试1. 代码展示2. 调试写到最后前言 针对嵌入式开发,有时候通过QT或PAD跨网络对设备设置WIFI,在此记录下,方便后续的查阅。 基础知识点 1.创建和删…...
PostgreSQL 变化数据捕捉(CDC)
PostgreSQL 变化数据捕捉(CDC)基于CDC(变更数据捕捉)的增量数据集成总体步骤:1.捕获源数据库中的更改数据2.将变更的数据转换为您的消费者可以接受的格式3.将数据发布到消费者或目标数据库PostgreSQL支持触发器&#x…...
Spring 事务【隔离级别与传播机制】
Spring 事务【隔离级别与传播机制】🍎一.事务隔离级别🍒1.1 事务特性回顾🍒1.2 事务的隔离级别(5种)🍒1.3 事务隔离级别的设置🍎二.Spring 事务传播机制🍒2.1 Spring 事务传播机制的作用🍒2.2 事…...
HTTP和HTTPS协议
HTTP协议 HTTP协议是一种应用层的协议,全称为超文本传输协议。 URL URL值统一资源定位标志,也就是俗称的网址。 协议方案名 http://表示的就是协议方案名,常用的协议有HTTP协议、HTTPS协议、FTP协议等。HTTPS协议是以HTTP协议为基础&#…...
day3——有关java运算符的笔记
今天主要学习的内容有java的运算符 赋值运算符算数运算符关系运算符逻辑运算符位运算符(专门写一篇笔记)条件运算符运算符的优先级流程控制 赋值运算符 赋值运算符()主要用于给变量赋值,可以跟算数运算符相结合&…...
Git多人协同远程开发
1. 李四(项目负责人)操作步骤 在github中创建远程版本库testgit将基础代码上传⾄testgit远程库远程库中基于main分⽀创建dev分⽀将 githubleaflife/testgit 共享给组员李四继续在基础代码上添加⾃⼰负责的模块内容 2. 张三、王五(组员&…...
Chapter4:机器人仿真
ROS1{\rm ROS1}ROS1的基础及应用,基于古月的课,各位可以去看,基于hawkbot{\rm hawkbot}hawkbot机器人进行实际操作。 ROS{\rm ROS}ROS版本:ROS1{\rm ROS1}ROS1的Melodic{\rm Melodic}Melodic;实际机器人:Ha…...
python(14)--集合
前言 本篇文章学习的是 python 中集合的基础知识。 集合元素的内容是不可变的,常见的元素有整数、浮点数、字符串、元组等。至于可变内容列表、字典、集合等不可以是集合元素。虽然集合不可以是集合的元素,但是集合本身是可变的,可以去增加或…...
【Spark分布式内存计算框架——Spark Core】4. RDD函数(中)Transformation函数、Action函数
3.2 Transformation函数 在Spark中Transformation操作表示将一个RDD通过一系列操作变为另一个RDD的过程,这个操作可能是简单的加减操作,也可能是某个函数或某一系列函数。值得注意的是Transformation操作并不会触发真正的计算,只会建立RDD间…...
Mysql 数据类型
1、数值数据类型 1.1 整数类型(精确值) INTEGER, INT, SMALLINT, TINYINT, MEDIUMINT, BIGINT MySQL支持SQL标准的整数类型INTEGER (或INT)和SMALLINT。作为标准的扩展,MySQL还支持整数类型TINYINT、MEDIUMINT和BIGINT。下表显示了每种整数类型所需的存储和范围。…...
运行Whisper笔记(1)
最近chatGPT很火,就去逛了一下openai的github项目。发现了这个项目。 这个项目可以识别视频中的音频,转换出字幕。 带着一颗好奇的心就尝试自己去部署玩一玩 跟着这篇文章一步步来进行安装,并且跟着这篇文章解决途中遇到的问题。 途中还会遇…...
2023年最强大的12款数据可视化工具,值得收藏
做数据分析也有年头了,好的坏的工具都用过,推荐几个觉得很好用的,避坑必看! PS:一般比较成熟的公司里,数据分析工具不只是满足业务分析和报表制作,像我现在给我们公司选型BI工具,是做…...
LeetCode刷题系列 -- 523. 连续的子数组和
给你一个整数数组 nums 和一个整数 k ,编写一个函数来判断该数组是否含有同时满足下述条件的连续子数组:子数组大小 至少为 2 ,且子数组元素总和为 k 的倍数。如果存在,返回 true ;否则,返回 false 。如果存…...
LeetCode刷题系列 -- 525. 连续数组
给定一个二进制数组 nums , 找到含有相同数量的 0 和 1 的最长连续子数组,并返回该子数组的长度。示例 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.创建自定义拦截器,实现 HandlerInterceptor 接口并重写 preHandle(执行具体方法之前的预处理)方法 1.3.2.将自定义拦…...
centos7.6 设置防火墙
1、查看系统版本 cat /etc/redhat-release2、查看防火墙运行状态 systemctl status firewalld这里可以看到当前是未运行状态(inactive)。 3、关闭开机自启动防火墙 systemctl disable firewalld.service4、启动防火墙并查看状态,系统默认 22 端口是开启的。 sy…...
多模态2025:技术路线“神仙打架”,视频生成冲上云霄
文|魏琳华 编|王一粟 一场大会,聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中,汇集了学界、创业公司和大厂等三方的热门选手,关于多模态的集中讨论达到了前所未有的热度。其中,…...
《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》
引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...
第25节 Node.js 断言测试
Node.js的assert模块主要用于编写程序的单元测试时使用,通过断言可以提早发现和排查出错误。 稳定性: 5 - 锁定 这个模块可用于应用的单元测试,通过 require(assert) 可以使用这个模块。 assert.fail(actual, expected, message, operator) 使用参数…...
EtherNet/IP转DeviceNet协议网关详解
一,设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络,本网关连接到EtherNet/IP总线中做为从站使用,连接到DeviceNet总线中做为从站使用。 在自动…...
dify打造数据可视化图表
一、概述 在日常工作和学习中,我们经常需要和数据打交道。无论是分析报告、项目展示,还是简单的数据洞察,一个清晰直观的图表,往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server,由蚂蚁集团 AntV 团队…...
【Java学习笔记】BigInteger 和 BigDecimal 类
BigInteger 和 BigDecimal 类 二者共有的常见方法 方法功能add加subtract减multiply乘divide除 注意点:传参类型必须是类对象 一、BigInteger 1. 作用:适合保存比较大的整型数 2. 使用说明 创建BigInteger对象 传入字符串 3. 代码示例 import j…...
#Uniapp篇:chrome调试unapp适配
chrome调试设备----使用Android模拟机开发调试移动端页面 Chrome://inspect/#devices MuMu模拟器Edge浏览器:Android原生APP嵌入的H5页面元素定位 chrome://inspect/#devices uniapp单位适配 根路径下 postcss.config.js 需要装这些插件 “postcss”: “^8.5.…...
Java + Spring Boot + Mybatis 实现批量插入
在 Java 中使用 Spring Boot 和 MyBatis 实现批量插入可以通过以下步骤完成。这里提供两种常用方法:使用 MyBatis 的 <foreach> 标签和批处理模式(ExecutorType.BATCH)。 方法一:使用 XML 的 <foreach> 标签ÿ…...
接口自动化测试:HttpRunner基础
相关文档 HttpRunner V3.x中文文档 HttpRunner 用户指南 使用HttpRunner 3.x实现接口自动化测试 HttpRunner介绍 HttpRunner 是一个开源的 API 测试工具,支持 HTTP(S)/HTTP2/WebSocket/RPC 等网络协议,涵盖接口测试、性能测试、数字体验监测等测试类型…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
