zoukankan      html  css  js  c++  java
  • CAS和AQS

    CAS  

      CAS的全称为Compare-And-Swap,它是一条CPU并发原语。它的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的。CAS并发原语体现在JAVA语言中就是sun.misc.Unsafe类中的各个方法。调用UnSafe类中的CAS方法,JVM会帮我们实现出CAS汇编指令。这是一种完全依赖于硬件的功能。那么为什么CAS会出现呢?它的作用是怎样的?
    实现并发的传统方式是加锁,JAVA中的锁有synchronized和Lock。Lock是基于AQS和CAS实现的,在此先不叙述。对于synchronized锁,JVM在执行它的时候会依赖操作系统的临界区机制。这样的话,每次执行到synchronized锁,都会经历用户态和内核态之间的切换。这个过程的消耗是很大的。而且,大多数时候synchronized锁住的操作是很细粒度的。为了细粒度的操作去经历用户态和内核态之间的切换是低效的做法。
      大家知道,当线程创建和销毁的时间大于任务执行的时间时,就需要考虑使用线程池了。但如果和任务执行时间相比,线程创建和销毁的时间很少,那么线程池也可不用。
    在synchronized中就是这个问题,当需要同步的操作粒度很细时,使用synchronized是不高效的,这时就有CAS存在的意义了。比如对于i++这种并发计数功能,使用synchronized就大材小用了,而使用CAS来实现就会更加的轻量级,性能更好。因此可以看到java.util.concurrent.atomic包中有类似AtomicInteger这种类。我们来看下AtomicInteger类的核心源码:

    复制代码
    public class AtomicInteger extends Number implements java.io.Serializable {
    
        // setup to use Unsafe.compareAndSwapInt for updates
        private static final Unsafe unsafe = Unsafe.getUnsafe();
        private static final long valueOffset;
    
        static {
          try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
          } catch (Exception ex) { throw new Error(ex); }
        }
    
        private volatile int value;
    
        public AtomicInteger(int initialValue) {
            value = initialValue;
        }
    
        public AtomicInteger() {
        }
    
        public final int get() {
            return value;
        }
    
        public final boolean compareAndSet(int expect, int update) {
            return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
        }
    
        public final int incrementAndGet() {
            for (;;) {
                int current = get();
                int next = current + 1;
                if (compareAndSet(current, next))
                    return next;
            }
        }
    }
    复制代码

    使用原子类解决线程安全问题,CAS并不能替代synchronized

    复制代码 
    
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.atomic.AtomicInteger;
    
    public class Test1 {
        private static AtomicInteger count = new AtomicInteger(0);
    
        public   static void add() {
            count.incrementAndGet();
            System.out.println(count);
        }
    
        static CountDownLatch downLatch = new CountDownLatch(100);
    
        public static void main(String[] args) throws InterruptedException {
    
            for (int i = 0; i < 100; i++) {
    
                new Thread(() -> {
                    for (int j = 0; j < 10; j++) {
                        add();
                    }
                    downLatch.countDown();
                }).start();
    
            }
            downLatch.await();
            System.out.println(count);
    
        }
    }
    复制代码

    这样最终结果会是一千但是无法保证线程执行的顺序,使用synchronized进行同步的话效率比较低但是能保证线程是安全的,也可以有序的执行

      在上面的代码中Unsafe类负责执行CAS并发原语,由JVM转化为汇编。在代码中使用CAS自旋volatile变量的形式实现非阻塞并发。这种方式是CAS的主要使用方式。
      CAS操作包含三个操作数——内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在CAS指令之前返回该位置的值。CAS有效地说明了“我认为位置V应该包含值A;如果包含该值,则将B放到这个位置;否则,不要更改该位置。无论如何,告诉我原值。
      Java.util.concurrent.atomic包为开发者提供了比synchronized更加细粒度的并发代码控制方式。实际上也是最细粒度的并发代码控制方式。
      CAS是乐观锁,是一种冲突重试的机制,在并发竞争不是很激烈的情况下(也是大多数情况),他的性能要好于基于锁的并发性能。因为并发竞争激烈的话,冲突重试的过程很多。

    AQS

    AQS原理 

     AQS:AbstractQuenedSynchronizer抽象的队列式同步器。是除了java自带的synchronized关键字之外的锁机制。
    AQS的全称为(AbstractQueuedSynchronizer),这个类在java.util.concurrent.locks包

    J.U.C是基于AQS实现的,AQS是一个同步器,设计模式是模板模式。
    核心数据结构:双向链表 + state(锁状态)
    底层操作:CAS

    AQS的核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并将共享资源设置为锁定状态,如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
    CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列,虚拟的双向队列即不存在队列实例,仅存在节点之间的关联关系。
    AQS是将每一条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node),来实现锁的分配。

    用大白话来说,AQS就是基于CLH队列,用volatile修饰共享变量state,线程通过CAS去改变状态符,成功则获取锁成功,失败则进入等待队列,等待被唤醒。

    **注意:AQS是自旋锁:**在等待唤醒的时候,经常会使用自旋(while(!cas()))的方式,不停地尝试获取锁,直到被其他线程获取成功

    实现了AQS的锁有:自旋锁、互斥锁、读锁写锁、条件产量、信号量、栅栏都是AQS的衍生物

    AQS中的int类型的state值,这里就是通过CAS(乐观锁)去修改state的值。lock的基本操作还是通过乐观锁来实现的。
    获取锁通过CAS,那么没有获取到锁,等待获取锁是如何实现的?我们可以看一下else分支的逻辑,acquire方法:

    1. tryAcquire:会尝试再次通过CAS获取一次锁。
    2. addWaiter:通过自旋CAS,将当前线程加入上面锁的双向链表(等待队列)中。
    3. acquireQueued:通过自旋,判断当前队列节点是否可以获取锁。
  • 相关阅读:
    Android的NDK开发(5)————Android JNI层实现文件的read、write与seek操作
    android Context
    android 控件放在 listview 的下方 并且在 屏幕底部
    android Activity 布局 和 控件属性
    有关vtun和虚拟网卡要做的实验
    android xml pull 解析 豆瓣书籍
    android UI设计之 背景透明色 项目资源文件关系
    android 资源引用 自定义标题栏
    真机调试Unable to open sync connection!
    C++ 编译预处理
  • 原文地址:https://www.cnblogs.com/wangdayexinyue/p/12576698.html
Copyright © 2011-2022 走看看