java并发 - 学习CAS

学习CAS

一丶什么是CAS

   CAS(Compare And Swap) 即比较交换, 给定一个期望值, 当将要修改的变量值和期望值一致时(即其他线程没有修改), 就修改对应的变量值, 否则什么也不做, 它允许再次尝试比较交换, 直到成功为止.

 

二丶CAS算法过程

  CAS(V,E,N). V表示要更新的变量, E表示预期值, N表示将要更新的值.

  仅当V=E时, 才会将V更新为N ,  如果 V ≠ E, 说明有其他线程更新了V, 当前线程什么也不用做. 最后CAS返回当前V的真实值

 

三丶悲观锁与乐观锁

  对于并发控制而言, 锁是一种悲观策略, 它总是认为多线程操作有可能冲突, 因此总要加锁控制,阻塞等待.

   无锁则是一种乐观策略, 它总是认为多线程操作很少会冲突, 无须加锁, 如果有冲突, 再次重新尝试操作直到没有冲突即可.

  无锁主要是使用CAS实现的, 它是一种"乐观锁"

   

  无锁的优点:

  1. 不会产生死锁的问题

  2. 没有锁竞争带来的系统开销

  3. 没有线程间的频繁调度带来的开销

  4. 性能更优越

  

 四丶CPU指令支持

  在硬件层面, 大部分的现代处理器都已经支持原子化的CAS指令.在JDK5.0以后, 虚拟机便可以使用这个指令来实现并发操作和并发数据结构, 并且, 这种操作在虚拟机中可以说是无处不在.

   

五丶unsafe类

   源码入口 AtomicInteger的compareAndSet()方法  (JDK 8)

 /**
     * Atomically sets the value to the given updated value
     * if the current value {@code ==} the expected value.
     *
     * @param expect the expected value
     * @param update the new value
     * @return {@code true} if successful. False return indicates that
     * the actual value was not equal to the expected value.
     */
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

  

  unsafe是一个不安全类, jdk不允许开发者直接使用.它的内部方法可以像指针一样直接操作内存. 资料 -- <java并发变成-无锁CAS>

  unsafe的compareAndSwapInt()方法, 内部使用CAS原子指令来完成

public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);

第一个参数o为给定的对象, offset为对象内的偏移量(通过偏移量,可以快速定位获取对应的字段变量), expected表示期望值, x表示要设置的值.如果指定的字段的值等于expected, 那么就会把它设置为x

  

 六丶Atomic原子类

   Atomic原子类都是使用CAS操作来实现线程安全操作的, 底层调用unsafe类方法, unsafe则使用了CAS原子指令

  以AtomicInteger为例, 内部记录维护一个使用volatile关键字修饰的int变量, (volatitle只保证线程间的可见性,即一个线程修改该变量, 另一个线程能够知道, 但它并不保证原子性)

   AtomicInteger的线程安全自增方法

/**
     * Atomically increments by one the current value.
     *
     * @return the updated value
     */
    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }

 

  unsafe中的getAndAddInt()方法

    public final int getAndAddInt(Object o, long offset, int delta) {
        int curr;
        do {
            curr = this.getIntVolatile(o, offset);  // 由于使用了volatile, 修改操作能够被各线程感知
        } while(!this.compareAndSwapInt(o, offset, curr, curr + delta));  //如果cas操作没有成功, 则继续尝试, compareAndSwapInt()方法见第五小节解释

        return curr;
    }

 

 

七丶ABA问题

   假设一个变量v初始值为0, t1,t2线程读取的值都为0, 由于线程调度等其他问题, t1并未继续cas操作,  期间t2使用cas操作, 将v加1变成1, 线程t3使用cas操作将v减1变成0, 之后t1获得cpu继续执行, t1使用cas操作时, 发现v值仍为0, 以为v没有变化, 实际已发生变化, 这样在某些场景就会出现问题, 这就是ABA问题

   使用带有时间戳的对象引用AtomicStampedReference可以有效解决该问题, AtomicStampedReference内部除了维护对象值, 还维护了一个时间戳(实际可以是任何一个整数标识来表示状态值, 也可以是版本号). AtomicStampedReference在更新对象值之外,还必须更新时间戳,在更新对象值前, 除了比较对象值之外,还必须比较时间戳, 只有这两个条件都相等的情况下,才会去更新值,否则认为被其他线程修改

 

八丶示例

 

public class AddCASRunnable implements Runnable {
    private AtomicInteger data =new AtomicInteger(0);


    public int getResult(){
        return data.get();
    }

    @Override
    public void run() {
        for(int i=0;i < 10000000; i++){
            data.incrementAndGet();
        }
    }
}

 

public class AddUnsafeRunnable implements Runnable {
    private int data=0;

    public int getResult(){
        return data;
    }

    @Override
    public void run() {
        for(int i=0;i < 10000000; i++){
            data++;
        }
    }
}

 

public class Client {

    public static void main(String[] args) throws InterruptedException {

        AddCASRunnable addCASRunnable=new AddCASRunnable();
        Thread t1=new Thread(addCASRunnable);
        Thread t2=new Thread(addCASRunnable);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("CAS 结果为: "+addCASRunnable.getResult()); // CAS 结果为: 20000000


        AddUnsafeRunnable addUnsafeRunnable=new AddUnsafeRunnable();
        Thread t3=new Thread(addUnsafeRunnable);
        Thread t4=new Thread(addUnsafeRunnable);
        t3.start();
        t4.start();
        t3.join();
        t4.join();
        System.out.println("unsafe 结果为: "+addUnsafeRunnable.getResult()); // unsafe 结果为: 19994931

    }

}

 

  源码地址为: https://gitee.com/timfruit189/java-learning/tree/master/src/main/java/com/ttx/java/concurrent/cas

 

 

学习资料:

  <java高并发程序设计> 葛一鸣 郭超 编著

  <java并发变成-无锁CAS>

 

posted @ 2019-06-08 11:20  timfruit  阅读(188)  评论(0编辑  收藏  举报