zoukankan      html  css  js  c++  java
  • CAS自旋锁

    四、 CAS自旋锁(Compare And Swap)

    思考一个问题:i++是否是原子性的?

    分析i++的操作过程:

    1. 内存读取数据写到寄存器
    2. 寄存器进行自增操作
    3. 寄存器将值写回内存

    经过上面分析可以知道,i++不是原子性的。那么如何使用多线程进行i++操作保证原子性?

    上一节学习了synchronized可以保证原子性,因此我们可以使用synchronized实现:

    import java.util.concurrent.TimeUnit;
    
    /**
     * @author 赵帅
     * @date 2021/1/13
     */
    public class SynchronizedIncrementDemo {
    
        private Integer num = 0;
    
        public void fun1() {
            synchronized (this) {
                for (int i = 0; i < 1000; i++) {
                    num++;
                }
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            SynchronizedIncrementDemo demo = new SynchronizedIncrementDemo();
    
            for (int i = 0; i < 10; i++) {
                new Thread(demo::fun1).start();
            }
    
            TimeUnit.SECONDS.sleep(1);
            System.out.println(demo.num);
        }
    }
    

    synchronized是通过加锁的方式保证原子性的。其实在操作系统底层是有CAS原语来保证原子性的。

    AtomicInteger

    AtomicInteger就是通过CAS实现的,上面代码用AtomicInteger实现为:

    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * @author 赵帅
     * @date 2021/1/13
     */
    public class AtomicIncrementDemo {
        private static AtomicInteger num = new AtomicInteger(0);
    
        public static void main(String[] args) throws InterruptedException {
            for (int i = 0; i < 10; i++) {
                new Thread(()->{
                    for (int j = 0; j < 1000; j++) {
                        num.getAndIncrement();
                    }
                }).start();
            }
    
            TimeUnit.SECONDS.sleep(1);
            System.out.println("num = " + num);
        }
    }
    

    什么是CAS

    compare and swap比较并交换

    在使用synchronized保证原子性时,是多个线程竞争同一把锁,同一时刻只有一个线程执行代码,以此保证原子性,但是当加上synchronized时就相当于多线程串形执行了,效率可能会变低(synchronized经过锁升级后,效率提高很多,并不一定比CAS低)。除了synchronized可以保证原子性,在cpu级别有compxchg指令,这个指令也可以保证原子性。

    执行 compxchg指令的时候,会判读当前系统是否为多核CPU,如果是多核CPU,那么就会给总线加锁,保证只有一个线程能给总线加锁成功,加锁之后进行CAS操作,因此可以说CAS也是排他锁,不过相比synchronized,CAS属于CPU原语级别,因此多线程情况下效率会高一点。

    CAS的工作原理

    当我们要执行一个i++的操作时,如果用CAS去执行的话,那么执行的过程,大概就是:

    1. i放在内存中,值为1
    2. 加载i到cpu中,并设置为旧的值old,载进行+1计算的到新的值new
    3. 再次拿内存中的i值,得到一个期望值expect。
    4. 然后我们拿expect与old值就行比较,如果相等的话,就说明这个值没有在我计算的过程中没有被改变过,那么就将i的值更新为新的值。如果expect与old不相等,那么就说明i的值在我计算的过程中被修改了,那么就重新进入步骤2。

    观察上面的流程,会发现整个执行过程会不断的在2~5之间进行循环,直到设置新的值成功为止,这个循环过程,线程并没有进入等待队列,就好像一直在原地转圈,因此CAS也叫自旋锁。

    LongAdder

    除了使用AtomicInteger,还可以使用LongAdder来保证原子性:

    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.atomic.LongAdder;
    
    /**
     * @author 赵帅
     * @date 2021/1/13
     */
    public class LongAdderDemo {
    
        public static LongAdder num = new LongAdder();
        public static void main(String[] args) throws InterruptedException {
            for (int i = 0; i < 10; i++) {
                new Thread(()->{
                    for (int j = 0; j < 1000; j++) {
                        num.increment();
                    }
                }).start();
            }
    
            TimeUnit.SECONDS.sleep(1);
            System.out.println("num = " + num);
        }
    }
    

    LongAdder是通过分段锁来实现的,因此在数据量非常大的时候,效率会比较高。synchronized,AtomicInteger,LongAdder都可以实现自增操作。他们之间的效率需要根据具体业务来选择,因为Synchronized经过锁升级的过程,效率并不一定比CAS低。

    java中还可以通过AtomicReference<>类来创建一个保证原子性的对象。

    Unsafe

    查看AtomicInteger的getAndIncrement()方法源码:

    		public final int getAndIncrement() {
            return unsafe.getAndAddInt(this, valueOffset, 1);
        }
    

    可以看到最终使用的是Unsafe类来实现的。

    什么是Unsafe

    Unsafe是java底层sun.misc包下的一个类,是一个线程不安全的类,可以提供一些用于执行级别低,不安全的操作方法。如:直接访问系统内存资源,自主管理内存资源等。这些方法在提升java运行效率,增强java语言底层资源操作能力方面起到了很大的作用。但是由于Unsafe类使java语言拥有了类似C语言指针一样操作内存空间的能力,因此也增加了内存安全的风险。在程序中过度,不正确的使用Unsafe类,会使得程序出错的概率变大,是的java这个安全的语言变得不再安全。

    Unsafe提供的API大致可分为:内存操作,CAS,Class,对象操作,线程调度,系统信息获取,内存屏障,数组操作等

    Unsafe的使用

    查看Unsafe的源码:

    		
    		private Unsafe() {
        }
    
        @CallerSensitive
        public static Unsafe getUnsafe() {
            Class var0 = Reflection.getCallerClass();
            // 仅在引导类加载器 BootstrapClassLoader 加载时才合法
            if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
                throw new SecurityException("Unsafe");
            } else {
                return theUnsafe;
            }
        }
    

    可以看出Unsafe是一个单例对象,而且无法通过getUnsafe来获取Unsafe对象,因为只有在BootstrapClassLoader加载器加载时才有效,否则会抛出SecurityExecuption异常。

    如果我们想要使用这个类时,可以通过反射来获取 theUnsafe属性来获取Unsafe对象实例。

    public class UnsafeDemo {
    
        public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            Unsafe unsafe = (Unsafe) field.get(null);
            System.out.println("unsafe = " + unsafe);
        }
    }
    

    使用 Unsafe进行CAS操作

    在unsafe类中操作CAS的方法有:

        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);
    
    
  • 相关阅读:
    Tcpdump抓包
    关于Adroid Bitmap OutOfMemoryError的问题解决
    java用substring函数截取string中一段字符串
    偶耶DIY布偶成都实体店开业
    瑞士Kardex(卡迪斯)自动化仓储货柜,Shuttle XP系列升降库驱动监控系统
    360顽固木马专杀工具 千万别用 会删除Oracle服务
    天上人和酒店管理系统(.net3.5 + sql2000 + linq to sql)
    [转]VC++中CListCtrl listcontrol用法技巧
    [转]孙鑫教程学习笔记
    [转]VC2005从开发MFC ActiveX ocx控件到发布到.net网站的全部过程
  • 原文地址:https://www.cnblogs.com/Zs-book1/p/14284814.html
Copyright © 2011-2022 走看看