欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

JUC ---- 原子操作 CAS

程序员文章站 2022-05-03 18:36:42
...
1. CAS产生的背景

JDK其实在语言层级上就用Synchronized实现了线程安全,那为什么还要在1.5的版本后引入原子操作呢?
sync是基于阻塞的锁机制,它可能会带来以下问题:

  1. 被阻塞的线程优先级高。
  2. 拿到锁的线程一直不释放锁
  3. 大量的竞争,消耗CPU, 同时带来死锁或者其他安全问题。
2. CAS
2.1 简介

CAS (Compare And Swap), 是通过自旋实现的,它的底层是一个native方法(有C++ 语言编写,来控制JVM)。其最终的实现上是通过一条CPU指令实现的(大概意思上,就是若存在多核处理任务,则锁住总线)。这也从计算机的原语级别上保证了这个操作的原子性。

CAS操作中的三个运算符:
一个内存地址V, 一个期望值 A, 一个新值B
基本思路:如果地址V上的值和期望的值A相等,就给地址V赋上新值B。如果不是,就在循环(死循环,自旋)里不断进行CAS操作。

2.2 CAS遇到的问题
  • ABA问题: A—> B —> A
    原来的值是A, 但它进过更改后,先从A 变成B, 再从B变成了A。 那么CAS进行检查时发现它的值没有变化,但是实际上却变化了。(某些应用场景下是不允许这种变化的发生的)
    ABA问题的解决思路使用版本号,在变量前面追加版本号,每次变量更新的时候版本号加1,那么A-B-A 就会变为 1A-2B-3A。Java中提供了原子类AtomicStampedReference来解决这个问题(它表示动过几次),而AtomicMarkableReference则是使用boolean表示有没有动过。
  • 开销问题:CAS操作长期不成功,那么CPU会在这上面不断地自旋消耗大量资源。
  • 只能保证一个共享变量的原子操作
2.3 JDK中相关的原子操作类
  • 更新基本类型:AtomicBoolean, AtomicInteger, AtomicLong
  • 更新数组类型:AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray
  • 更新引用类型:AtomicReference, AtomicMarkableReference, AtomicStampedReference(解决ABA问题)
  • 原子更新字段类:AtomicReferenceFieldUpdater, AtomicIntegerFieldUpdater, AtomicLongFieldUpdater (使用相对较少)

由于使用方式比较类似,这里以AtomicStampedReference为例子进行演示

3. 原子类的使用

这里以两个线程为例子,线程1使用正确的版本戳修改完引用后,线程2再使用老的版本号进行修改,这时会发现修改失败(因为版本号不对应)

package atomic;

import java.util.concurrent.atomic.AtomicStampedReference;

public class UseAtomicStampedReference {
    //新建一个带版本号和引用类型的原子变量
    static AtomicStampedReference<String> asr = new AtomicStampedReference<>("gs",0);

    public static void main(String[] args) throws Exception {
        final int oldStamp = asr.getStamp(); //初始化的版本号
        final String oldReference = asr.getReference();

        System.out.println(oldReference+"=========="+oldStamp);

        //使用正确版本号的更改的线程
        Thread rightStamp = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()
                +"当前变量值:"+ oldReference + ",当前版本号为:"+oldStamp
                +"-"+asr.compareAndSet(oldReference,oldReference+" Love you",
                        oldStamp,oldStamp+1));
            }
        });
		//使用老版本号修改的线程
        Thread errorStamp = new Thread(new Runnable() {
        	//此时不能再使用oldStamp,因为版本号发生了变化
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()
                        +"当前变量值:"+ asr.getReference() + ",当前版本号为:"+asr.getStamp()
                        +"-"+asr.compareAndSet(asr.getReference(),asr.getReference()+" Love you",
                        oldStamp,oldStamp+1));
            }
        });

       rightStamp.start();
       //这里表示rightStamp的线程先运行完再运行errorStamp的线程
       rightStamp.join();
       errorStamp.start();
       errorStamp.join();

       System.out.println(asr.getReference()+"-----"+asr.getStamp());


    }
}

控制台的输出
JUC ---- 原子操作 CAS

相关标签: JUC