• java并发


    学习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>

     

    人生没有彩排,每一天都是现场直播
  • 相关阅读:
    toj4119HDFS
    hdu2952Counting Sheep
    hdu2393Higher Math
    hdu2317Nasty Hacks
    hdu2309ICPC Score Totalizer Software
    hdu2304Electrical Outlets
    hdu2399GPA
    一、 软件测试概述
    JQuery选择器大全
    如何避免jQuery库和其他库的冲突
  • 原文地址:https://www.cnblogs.com/timfruit/p/10962986.html
Copyright © 2020-2023  润新知