并发编程之CAS和Atomic

Atomic与CAS以及产生的ABA问题

  • 什么是原子操作
  • Atomic
    • 先来看个例子了解下
    • CAS
    • 给大家看个手写CAS例子
    • Automic三大API
    • AtomicIntegerArray
    • AtomicReferenceArray
    • AtomicIntegerFieldUpdater
    • AtomicReferenceFieldUpdater
    • ABA问题及解决
    • 内存屏障

什么是原子操作

原子(atom)本意是“不能被进一步分割的最小粒子”,而原子操作(atomic operation)意为”不可被中断的一个或一系列操作” 。Java里是如何实现原子操作

在java中可以通过锁和循环CAS的方式来实现原子操作。
JVM中的CAS操作正是利用了处理器提供的CMPXCHG指令实现的。自旋CAS实现的基本思路就是循环进行CAS操作直到成功为止,具体的类可以参见juc下的 atomic包内的原子类。

Atomic

在Atomic包里一共有12个类,四种原子更新方式,分别是原子更新基本类型,原子更新数组,原子更新引用和原子更新字段。
Atomic包里的类基本都是使用Unsafe实现的包装类。

  • 基本类:AtomicInteger、AtomicLong、AtomicBoolean;
  • 引用类型:AtomicReference、AtomicReference的ABA实例、
    AtomicStampedRerence、AtomicMarkableReference;
  • 数组类型:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
  • 属性原子修改器(Updater):AtomicIntegerFieldUpdater、
    AtomicLongFieldUpdater、AtomicReferenceFieldUpdater

先来看个例子了解下

public class T1_AtomicInteger {

    public static int total = 0;
    static AtomicInteger atomic = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(10);
        for(int i=0;i<10;i++){
            new Thread(()->{
                for(int j=0;j<1000;j++){
                    /*synchronized () {
                        total++;//cas
                    }*/
                    atomic.getAndIncrement();
                }
                countDownLatch.countDown();
            }).start();
        }
        countDownLatch.await();
        System.out.println(atomic.get());
    }
}

这里的AtomicInteger的自增方法是无锁操作,也就是CAS。效率性能上都要比synchronized要好。

CAS

CAS就是compare and swap。比较与交换。那我们通过AtomicInteger分析

do {
    oldvalue = this.getIntVolatile(var1, var2);//读AtomicInteger的value值
    ///valueOffset---value属性在对象内存当中的偏移量
} while(!this.compareAndSwapInt(AtomicInteger, valueOffset, oldvalue, oldvalue + 1));
return var5;

并发编程之CAS和Atomic_第1张图片
读到主内存的值M,复制到工作线程中变为N,比较主内存的M和工作线程的值N是否相等,不相等自旋直到相等。相等的话更新为计算值V。

什么叫偏移量?
要用cas修改某个对象属性的值->,首先要知道属性在对象的内存空间的哪个位置,必须知道属性的偏移量
内存中的寻址
并发编程之CAS和Atomic_第2张图片

给大家看个手写CAS例子

public class AtomicStudentAgeUpdater {
    private String name ;
    private volatile int age;

    public AtomicStudentAgeUpdater(String name,int age){
        this.name = name;
        this.age = age;
    }

    public int getAge(){
        return this.age;
    }

    public static void main(String[] args) {
        AtomicStudentAgeUpdater updater = new AtomicStudentAgeUpdater("杨过",18);

        System.out.println(ClassLayout.parseInstance(updater).toPrintable());

        updater.compareAndSwapAge(18,56);
        System.out.println("真实的杨过年龄---"+updater.getAge());
    }

    private static final Unsafe unsafe = UnsafeInstance.reflectGetUnsafe();
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset(AtomicStudentAgeUpdater.class.getDeclaredField("age"));
            System.out.println("valueOffset:--->"+valueOffset);
        } catch (Exception e) {
            throw new Error(e);
        }
    }

    public void compareAndSwapAge(int old,int target){
        unsafe.compareAndSwapInt(this,valueOffset,old,target);
    }

}

运行结果:
并发编程之CAS和Atomic_第3张图片

CAS的写法是通用的

  1. unsafe类取偏移量
  2. unsafe.compareAndSwapInt

Automic三大API

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

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

public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

如果说要原子修改的属性是一个Array?

AtomicIntegerArray

public class AtomicIntegerArrayRunner {

    static int[] value = new int[]{1,2};
    static AtomicIntegerArray aiArray = new AtomicIntegerArray(value);

    public static void main(String[] args) {
        //todo 原子修改数组下标0的数值
        aiArray.getAndSet(0,3);
        System.out.println(aiArray.get(0));
        System.out.println(value[0]);
}

}

运行结果:
并发编程之CAS和Atomic_第4张图片可以看到修改成功,但是原数组的值并没有改变。因为改的是副本:我们看源码

public AtomicIntegerArray(int[] array) {
        // Visibility guaranteed by final field guarantees
        this.array = array.clone();
}

AtomicReferenceArray

如果是对象数组怎么改:

@Data
public class Tuling {

    private Integer sequence;

    public Tuling(Integer seq){
        sequence = seq;
    }
}
public class AtomicReferenceArrayRunner {

    static Tuling[] ovalue = new Tuling[]{new Tuling(1),new Tuling(2)};
    static AtomicReferenceArray<Tuling> objarray = new AtomicReferenceArray(ovalue);

    public static void main(String[] args) {
        System.out.println(objarray.get(0).getSequence());

        objarray.set(0,new Tuling(3));

        System.out.println(objarray.get(0).getSequence());

    }

}

运行结果: 并发编程之CAS和Atomic_第5张图片

AtomicIntegerFieldUpdater

public class AtomicIntegerFieldUpdateRunner {

    static AtomicIntegerFieldUpdater aifu = AtomicIntegerFieldUpdater.newUpdater(Student.class,"old");

    public static void main(String[] args) {
        Student stu = new Student("杨过",18);
        //getAndIncrement返回的是原值,但是计算后的结果需要通过get方式获取
        System.out.println(aifu.getAndIncrement(stu));
        System.out.println(aifu.getAndIncrement(stu));
        System.out.println(aifu.get(stu));
    }

    static class Student{
        private String name;
        public volatile int old;

        public Student(String name ,int old){
            this.name = name;
            this.old = old;
        }

        public String getName() {
            return name;
        }

        public int getOld() {
            return old;
        }
    }

}

运行结果:并发编程之CAS和Atomic_第6张图片

AtomicReferenceFieldUpdater

public class AtomicReferenceFieldUpdaterRunner {

    static AtomicReferenceFieldUpdater atomic = AtomicReferenceFieldUpdater.newUpdater(Document.class,String.class,"name");

    public static void main(String[] args) {
        Document document = new Document("杨过",1);

        System.out.println(atomic.get(document));

        atomic.getAndSet(document,"xiaolongnv");

        System.out.println(atomic.get(document));

    }

    @Data
    static class Document{
        public volatile String name;
        private int version;

        Document(String obj,int v){
            name = obj;
            version = v;
        }

    }
}

运行结果:并发编程之CAS和Atomic_第7张图片

ABA问题及解决

举个场景:

  • 王百万: 打算往自己账户100w,先查一下自w己账户:有多少钱->100W,在柜台查的,撩妹->耽误时间(1小时),
  • 撩妹聊完了,又查了一下自己的户头,100w;
  • 张三:去老王账户100w->非法转入股票市场户口(此时老王账户0)->炒股做T的高手(低买高卖)–>150w->100W又转回老王的户头,张三赚了50w

ABA-》怎么解决?
可以通过版本号解决:每次修改版本号加1
A-0->B-1->A-2->B-3->A-4
AtomicStampedReference登场了

public class AtomicStampedRerenceRunner {

    private static AtomicStampedReference<Integer> atomicStampedRef =
            new AtomicStampedReference<>(1, 0);//初始值,版本号

    public static void main(String[] args){
        Thread main = new Thread(() -> {
            int stamp = atomicStampedRef.getStamp(); //获取当前标识别
            System.out.println("操作线程" + Thread.currentThread()+ "stamp="+stamp + ",初始值 a = " + atomicStampedRef.getReference());
            try {
                Thread.sleep(3000); //等待1秒 ,以便让干扰线程执行
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            boolean isCASSuccess = atomicStampedRef.compareAndSet(1,2,stamp,stamp +1);  //此时expectedReference未发生改变,但是stamp已经被修改了,所以CAS失败
            System.out.println("操作线程" + Thread.currentThread() + "stamp="+stamp + ",CAS操作结果: " + isCASSuccess);
        },"主操作线程");

        Thread other = new Thread(() -> {
            int stamp = atomicStampedRef.getStamp();
            atomicStampedRef.compareAndSet(1,2,stamp,stamp+1);
            System.out.println("操作线程" + Thread.currentThread() + "stamp="+atomicStampedRef.getStamp() +",【increment】 ,值 a= "+ atomicStampedRef.getReference());
            stamp = atomicStampedRef.getStamp();
            atomicStampedRef.compareAndSet(2,1,stamp,stamp+1);
            System.out.println("操作线程" + Thread.currentThread() + "stamp="+atomicStampedRef.getStamp() +",【decrement】 ,值 a= "+ atomicStampedRef.getReference());
        },"干扰线程");

        main.start();
        LockSupport.parkNanos(1000000);
        other.start();
    }
}

线程main先执行,线程other后执行。干扰线程执行完,主线程最后修改会发现修改失败。运行结果:
并发编程之CAS和Atomic_第8张图片

内存屏障

在Java 8中引入,用于定义内存屏障(也称内存栅栏,内存栅障,屏障指令等,是一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的 所有读写操作都执行后才可以开始执行此点之后的操作),避免代码重排序。volatile就是用到了内存屏障,避免了指令重排

你可能感兴趣的:(并发编程,多线程,java,thread)