精华内容
下载资源
问答
  • 场景 1:A 线程执行代码 1 代码 2,然后 B 线程执行代码 1 代码 2,CAS 成功。 场景 2:A 线程执行代码 1,此时 B 线程执行代码 1 代码 2,A 线程执行代码 2,CAS 不成功,为什么呢? 因为 A 线程执行代码 1...

    CAS(Compare And Swap)原理分析

    字面意思是比较和交换,先看看下面场景(A 和 B 线程同时执行下面的代码):

    int i = 10;  //代码 1
    i = 20;      //代码 2
    

    场景 1:A 线程执行代码 1 和代码 2,然后 B 线程执行代码 1 和代码 2,CAS 成功。

    场景 2:A 线程执行代码 1,此时 B 线程执行代码 1 和代码 2,A 线程执行代码 2,CAS 不成功,为什么呢?

    因为 A 线程执行代码 1 时候会旧值(i 的内存地址的值 10)保存起来,执行代码 2 的时候先判断 i 的最新值(可能被其他线程修改了)跟旧值比较,如果相等则把 i 赋值为 20,如果不是则 CAS 不成功。CAS 是一个原子性操作,要么成功要么失败,CAS 操作用得比较多的是 sun.misc 包的 Unsafe 类,而 Java 并发包大量使用 Unsafe 类的 CAS 操作,比如:AtomicInteger 整数原子类(本质是自旋锁 + CAS),CAS 不需加锁,提高代码运行效率。也是一种乐观锁方式,我们通常认为在大多数场景下不会出现竞争资源的情况,如果 CAS 操作失败,会不断重试直到成功。

    CAS 优点:资源竞争不大的场景系统开销小。

    CAS 缺点

    • 如果 CAS 长时间操作失败,即长时间自旋,会导致 CPU 开销大,但是可以使用 CPU 提供的 pause 指令,这个 pause 指令可以让自旋重试失败时 CPU 先睡眠一小段时间后再继续自旋重试 CAS 操作,jvm 支持 pause 指令,可以让性能提升一些。
    • 存在 ABA 问题,即原来内存地址的值是 A,然后被改为了 B,再被改为 A 值,此时 CAS 操作时认为该值未被改动过,ABA 问题可以引入版本号来解决,每次改动都让版本号 +1。Java 中处理 ABA 的一个方案是 AtomicStampedReference 类,它是使用一个 int 类型的字段作为版本号,每次修改之前都先获取版本号和当前线程持有的版本号比对,如果一致才进行修改操作,并把版本号 +1。
    • 无法保证代码块的原子性,CAS 只能保证单个变量的原子性操作,如果要保证多个变量的原子性操作就要使用悲观锁了。

    AQS(AbstractQueuedSynchronizer)原理分析

    字面意思是抽象的队列同步器,AQS 是一个同步器框架,它制定了一套多线程场景下访问共享资源的方案,Java 中很多同步类底层都是使用 AQS 实现,比如:ReentrantLock、CountDownLatch、ReentrantReadWriteLock,这些 java 同步类的内部会使用一个 Sync 内部类,而这个 Sync 继承了 AbstractQueuedSynchronizer 类,这是一种模板方法模式,所以说这些同步类的底层是使用 AQS 实现。

    面试官:CAS和AQS底层原理了解?我:一篇文章堵住你的嘴

     

    AQS 内部维护了一个 volatile 修饰的 int state 属性(共享资源)和一个先进先出的线程等待队列(即多线程竞争共享资源时被阻塞的线程会进入这个队列)。因为 state 是使用 volatile 修饰,所以在多线程之前可见,访问 state 的方式有 3 种,getState()、setState()和 compareAndSetState()。

    AQS 定义了 3 种资源共享方式:

    • 独占锁(exclusive),保证只有一条线程执行,比如 ReentrantLock、AtomicInteger。
    • 共享锁(shared),允许多个线程同时执行,比如 CountDownLatch、Semaphore。
    • 同时实现独占和共享,比如 ReentrantReadWriteLock,允许多个线程同时执行读操作,只允许一条线程执行写操作。

    ReentrantLock 和 CountDownLatch 都是自定义同步器,它们的内部类 Sync 都是继承了 AbstractQueuedSynchronizer,独占锁和共享锁的区别在于各自重写的获取和释放共享资源的方式不一样,至于线程获取资源失败、唤醒出队、中断等操作 AQS 已经实现好了。

    ReentrantLock

    state 的初始值是 0,即没有被锁定,当 A 线程 tryAcquire() 时会独占锁住 state,并且把 state+1,然后 B 线程(即其他线程)tryAcquire() 时就会失败进入等待队列,直到 A 线程 tryRelease() 释放锁把 state-1,此时也有可能出现重入锁的情况,state-1 后的值不是 0 而是一个正整数,因为重入锁也会 state+1,只有当 state=0 时,才代表其他线程可以 tryAcquire() 获取锁。

    CountDownLatch

    8 人赛跑场景,即开启 8 个线程进行赛跑,state 的初始值设置为 8(必须与线程数一致),每个参赛者跑到终点(即线程执行完毕)则调用 countDown(),使用 CAS 操作把 state-1,直到 8 个参赛者都跑到终点了(即 state=0),此时调用 await() 判断 state 是否为 0,如果是 0 则不阻塞继续执行后面的代码。

    tryAcquire()、tryRelease()、tryAcquireShared()、tryReleaseShared() 的详细流程分析

    tryAcquire() 详细流程如下:

    1. 调用 tryAcquire() 尝试获取共享资源,如果成功则返回 true;
    2. 如果不成功,则调用 addWaiter() 把此线程构造一个 Node 节点(标记为独占模式),并使用 CAS 操作把节点追加到等待队列的尾部,然后该 Node 节点的线程进入自旋状态;
    3. 线程自旋时,判断自旋节点的前驱节点是不是头结点,并且已经释放共享资源(即 state=0),自旋节点是否成功获取共享资源(即 state=1),如果三个条件都成立则自旋节点设置为头节点,如果不成立则把自旋节点的线程挂起,等待前驱节点唤醒。

    面试官:CAS和AQS底层原理了解?我:一篇文章堵住你的嘴

     

    tryRelease() 详细流程如下:

    1. 调用 tryRelease() 释放共享资源,即 state=0,然后唤醒没有被中断的后驱节点的线程;
    2. 被唤醒的线程自旋,判断自旋节点的前驱节点是不是头结点,是否已经释放共享资源(即 state=0),自旋节点是否成功获取共享资源(即 state=1),如果三个条件都成立则自旋节点设置为头节点,如果不成立则把自旋节点的线程挂起,等待被前驱节点唤醒。

    tryAcquireShared() 详细流程如下:

    1. 调用 tryAcquireShared() 尝试获取共享资源,如果 state>=0,则表示同步状态(state)有剩余还可以让其他线程获取共享资源,此时获取成功返回;
    2. 如果 state<0,则表示获取共享资源失败,把此线程构造一个 Node 节点(标记为共享模式),并使用 CAS 操作把节点追加到等待队列的尾部,然后该 Node 节点的线程进入自旋状态;
    3. 线程自旋时,判断自旋节点的前驱节点是不是头结点,是否已经释放共享资源(即 state=0),再调用 tryAcquireShared() 尝试获取共享资源,如果三个条件都成立,则表示自旋节点可执行,同时把自旋节点设置为头节点,并且唤醒所有后继节点的线程。
    4. 如果不成立,挂起自旋的线程,等待被前驱节点唤醒。

    tryReleaseShared() 详细流程如下:

    1. 调用 tryReleaseShared() 释放共享资源,即 state-1,然后遍历整个队列,唤醒所有没有被中断的后驱节点的线程;
    2. 被唤醒的线程自旋,判断自旋节点的前驱节点是不是头结点,是否已经释放共享资源(即 state=0),再调用 tryAcquireShared() 尝试获取共享资源,如果三个条件都成立,则表示自旋节点可执行,同时把自旋节点设置为头节点,并且唤醒所有后继节点的线程。
    3. 如果不成立,挂起自旋的线程,等待被前驱节点唤醒。

    最后

    觉得不错的小伙伴记得转发关注哦,后续会持续更新精选技术文章!

    展开全文
  • CAS和AQS

    2021-01-24 09:33:05
    CAS 全称(Compare And Swap),比较交换 Unsafe类是CAS的核心类,提供硬件级别的原子操作。 // 对象、对象的地址、预期值、修改值 public final native boolean compareAndSwapInt(Object var1, long var2, ...

    CAS

    全称(Compare And Swap),比较交换

    Unsafe类是CAS的核心类,提供硬件级别的原子操作

     

    // 对象、对象的地址、预期值、修改值
    public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
    

    缺点:

    1. 开销大:在并发量比较高的情况下,如果反复尝试更新某个变量,却又一直更新不成功,会给CPU带来较大的压力
    2. ABA问题:当变量从A修改为B在修改回A时,变量值等于期望值A,但是无法判断是否修改,CAS操作在ABA修改后依然成功。
      • 如何避免:Java提供了AtomicStampedReference和AtomicMarkableReference来解决。AtomicStampedReference通过包装[E,Integer]的元组来对对象标记版本戳stamp,对于ABA问题其解决方案是加上版本号,即在每个变量都加上一个版本号,每次改变时加1,即A —> B —> A,变成1A —> 2B —> 3A。
    3. 不能保证代码块的原子性:CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性。

     

    public class Test {
        private static AtomicInteger atomicInteger = new AtomicInteger(100);
        private static AtomicStampedReference atomicStampedReference = new AtomicStampedReference(100,1);
    
        public static void main(String[] args) throws InterruptedException {
    
            //AtomicInteger
            Thread at1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    atomicInteger.compareAndSet(100,110);
                    atomicInteger.compareAndSet(110,100);
                }
            });
    
            Thread at2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        TimeUnit.SECONDS.sleep(2);      // at1,执行完
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("AtomicInteger:" + atomicInteger.compareAndSet(100,120));
                }
            });
    
            at1.start();
            at2.start();
    
            at1.join();
            at2.join();
    
            //AtomicStampedReference
    
            Thread tsf1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        //让 tsf2先获取stamp,导致预期时间戳不一致
                        TimeUnit.SECONDS.sleep(2);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // 预期引用:100,更新后的引用:110,预期标识getStamp() 更新后的标识getStamp() + 1
                    atomicStampedReference.compareAndSet(100,110,atomicStampedReference.getStamp(),atomicStampedReference.getStamp() + 1);
                    atomicStampedReference.compareAndSet(110,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp() + 1);
                }
            });
    
            Thread tsf2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    int stamp = atomicStampedReference.getStamp();
    
                    try {
                        TimeUnit.SECONDS.sleep(2);      //线程tsf1执行完
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("AtomicStampedReference:" +atomicStampedReference.compareAndSet(100,120,stamp,stamp + 1));
                }
            });
    
            tsf1.start();
            tsf2.start();
        }
    
    }
    

    AQS(AbstractQueuedSynchronizer)

    维护一个volatile int state(代表共享资源状态)和一个FIFO线程等待队列。

    模板方法基本分为三类:

    • 独占锁
    • 共享锁
    • 释放锁

    资源共享的方式

    1. Exclusive(独占,只有一个线程能执行,如ReentrantLock)
    2. Share(共享,多个线程可以同时执行,如Semaphore/CountDownLatch)

    同步队列

    AQS依靠同步队列(一个FIFO的双向队列)来完成同步状态的管理。当当前线程获取状态失败后,同步器会将当前线程以及等待信息构造成一个节点(Node),并尝试将他加入到同步队列。Head节点不保存等待的线程信息,仅通过next指向队列中第一个保存等待线程信息的Node。

     

    双向同步队列

    Node类

    源码(中字注释)

     

    static final class Node {
        /** 代表共享模式 */
        static final Node SHARED = new Node();
        /** 代表独占模式 */
        static final Node EXCLUSIVE = null;
    
        /** 以下四个状态解释见下文等待状态 */
        static final int CANCELLED =  1;
        static final int SIGNAL    = -1;
        static final int CONDITION = -2;
        static final int PROPAGATE = -3;
    
        /** 标识等待状态,通过CAS操作更新,原子操作不会被打断*/
        volatile int waitStatus;
    
        /** 当前节点的前置节点 */
        volatile Node prev;
    
        /** 当前节点的后置节点 */
        volatile Node next;
    
        /** 该节点关联的线程(未能获取锁,进入等待的线程) */
        volatile Thread thread;
    
        /** 指向下一个在某个条件上等待的节点,或者指向 SHARE 节点,表明当前处于共享模式*/
        Node nextWaiter;
    
        /**
         * 判断是否处于共享模式
         */
        final boolean isShared() {
            return nextWaiter == SHARED;
        }
    
        /** 
         * 返回当前节点的前置节点 
         * 会做对前置节点空值判断 
         */
        final Node predecessor() throws NullPointerException {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }
    
        Node() {    // Used to establish initial head or SHARED marker
        }
    
        Node(Thread thread, Node mode) {     // Used by addWaiter
            this.nextWaiter = mode;
            this.thread = thread;
        }
    
        Node(Thread thread, int waitStatus) { // Used by Condition
            this.waitStatus = waitStatus;
            this.thread = thread;
        }
    }
    

    等待状态:

    等待状态的修改是CAS原子操作

    • CANCELED: 1,因为等待超时 (timeout)或者中断(interrupt),节点会被置为取消状态。处于取消状态的节点不会再去竞争锁,也就是说不会再被阻塞。节点会一直保持取消状态,而不会转换为其他状态。处于 CANCELED 的节点会被移出队列,被 GC 回收。
    • SIGNAL: -1,表明当前的后继结点正在或者将要被阻塞(通过使用 LockSupport.pack 方法),因此当前的节点被释放(release)或者被取消时(cancel)时,要唤醒它的后继结点(通过 LockSupport.unpark 方法)。
    • CONDITION: -2,表明当前节点在条件队列中,因为等待某个条件而被阻塞。
    • PROPAGATE: -3,在共享模式下,可以认为资源有多个,因此当前线程被唤醒之后,可能还有剩余的资源可以唤醒其他线程。该状态用来表明后续节点会传播唤醒的操作。需要注意的是只有头节点才可以设置为该状态(This is set (for head node only) in doReleaseShared to ensure propagation continues, even if other operations have since intervened.)。
    • 0:新创建的节点会处于这种状态

    锁的获取与释放:

    获取独占锁

     

    获取独占锁

    • acquire方法

     

    public final void acquire(int arg) {
        // 首先尝试获取锁,如果获取失败,会先调用 addWaiter 方法创建节点并追加到队列尾部
        // 然后调用 acquireQueued 阻塞或者循环尝试获取锁
        if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)){
            // 在 acquireQueued 中,如果线程是因为中断而退出的阻塞状态会返回 true
            // 这里的 selfInterrupt 主要是为了恢复线程的中断状态
            selfInterrupt();
        }
    }
    

    释放独占锁

     

    释放独占锁

    ​ 在独占模式中,锁的释放由于没有其他线程竞争,相对简单。锁释放失败的原因是由于该线程本身不拥有锁,而非多线程竞争。锁释放成功后会检查后置节点的状态,找到合适的节点,调用unparkSuccessor方法唤醒该节点所关联的线程。

    • release方法

     

    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            // waitStatus 为 0,证明是初始化的空队列或者后继结点已经被唤醒了
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
    

    获取共享锁

     

    获取共享锁

    • acquireShared方法

      通过该方法可以申请锁

     

    public final void acquireShared(int arg) {
        // 如果返回结果小于0,证明没有获取到共享资源
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }
    
    • doAcquireShared

     

    private void doAcquireShared(int arg) {
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
    

    释放共享锁



    作者:RealityVibe
    链接:https://www.jianshu.com/p/2a48778871a9
    来源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

    展开全文
  • CAS和AQS一文搞懂

    千次阅读 2021-03-12 14:35:36
    只有一个线程能执行,具体的JAVA实现有ReentrantLock 共享式:多个线程同时执行 AQS 是基于 volitale CAS 实现的,其中 AQS 中维护一个 valitale 类型的变量 state 来做一个可重入锁的重入次数,加锁释放锁也是...

    JAVA多线程,面试官喜欢问的东西

    一些概念的东西

    原子性就是指该操作是不可再分的。不论是多核还是单核,具有原子性的量,同一时刻只能有一个线程来对它进行操作。简而言之,在整个操作过程中不会被线程调度器中断的操作,都可认为是原子性。比如 a = 1;

    非原子性:也就是整个过程中会出现线程调度器中断操作的现象
    类似"a ++"这样的操作不具有原子性,因为它可能要经过以下两个步骤:

    (1)取出 a 的值

    (2)计算 a+1

    如果有两个线程t1,t2在进行这样的操作。t1在第一步做完之后还没来得及加1操作就被线程调度器中断了,于是t2开始执行,t2执行完毕后t1开始执行第二步(此时t1中a的值可能还是旧值,不是一定的,只有线程t2中a的值没有及时更新到t1中才会出现)。这个时候就出现了错误,t2的操作相当于被忽略了

    JAVA的线程调度—抢占式调度(优先级)


    CAS—(Compare And Swap)比较并交换

    采用乐观锁思想—总是认为自己可以完成操作

    在多个线程操作一个变量时,只有一个线程会成功更新,其他失败,失败线程允许重试

    CAS自旋等待

    用锁或 synchronized 关键字可以实现原子操作,那么为什么还要用 CAS 呢,因为加锁或使用 synchronized 关键字带来的性能损耗较大,而用 CAS 可以实现乐观锁,它实际上是直接利用了 CPU 层面的指令,所以性能很高。

    那么CAS 是怎么样实现自旋锁,CAS 利用 CPU 指令保证了操作的原子性,以达到锁的效果,至于自旋,一般是用一个无限循环实现。这样一来,一个无限循环中,执行一个 CAS 操作,当操作成功,返回 true 时,循环结束;当返回 false 时,接着执行循环,继续尝试 CAS 操作,直到返回 true。

    进入CAS源码看
    import java.util.concurrent.atomic.AtomicInteger;
    

    以原子方式将值设置为给定的更新值(update),如果当前值等于期望值(expect)。 返回ture,更新变量的值,实际值不等于期望值返回false,线程什么都不做,CAS返回当前变量值;

    public final boolean compareAndSet(int expect, int update) {
            return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
        }
    

    进去unsafe.compareAndSwapInt(this, valueOffset, expect, update);它是个 native方法,用 c++ 实现,有兴趣的可以去看看JDK源代码中safe.cpp文件,所以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);
    
    CAS产生的ABA问题

    什么是ABA?

    上面我可以知道,CAS算法实现有一个重要的前提:需要取出内存中某一时刻的数据,然后在下一时刻进行比较,替换,在这个时间差,数据可能已经发生变化,这就导致了ABA问题;

    解决方法:部分是通过加版本号(version)来解决,

    AQS是一个抽象的队列同步器

    AQS维护了一个volatile语义(支持多线程下的可见性)的共享资源变量state和一个FIFO线程等待队列(多线程竞争state被阻塞时会进入此队列)。java.util.concurrent.locks.AbstractQueuedSynchronizer 抽象类,简称 AQS

    img

    AQS共享资源的方式:独占式和共享式

    AQS默认提供了独占式和共享式两种模式,JDK对应的实现有ReentrantLockReentrantReadWriteLock

    独占式:只有一个线程能执行,具体的JAVA实现有ReentrantLock

    共享式:多个线程同时执行

    AQS 是基于 volitale 和 CAS 实现的,其中 AQS 中维护一个 valitale 类型的变量 state
    来做一个可重入锁的重入次数,加锁和释放锁也是围绕这个变量来进行的。

    在这里插入图片描述

    推荐AQS解析


    展开全文
  • 深入理解CAS和AQS

    2020-12-27 01:48:12
    CAS(compare and swap) 悲观锁乐观锁 悲观锁:悲观的认为每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁,比如synchronized数据库层面的 for...

    CAS(compare and swap)

    悲观锁和乐观锁

    悲观锁:悲观的认为每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁,比如synchronized和数据库层面的 for update,加锁和释放锁会导致比较多的上下文切换和调度延时,引起性能问题

    //保证方法每次只能被一个线程访问
    public synchronized void test() {}
    //select 不会主动加锁,for update加锁
    select * from tb where xxx for update
    

    乐观锁:乐观的认为每次去拿数据的时候都认为不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,使用版本号或CAS实现

    //atomic
    private AtomicInteger count = new AtomicInteger();
    public Counter(){}
    public int getCount(){
    	return count.get();
    }
    public void increase(){
    	count.getAndIncrement();
    }
    
    //版本号或者比对时间戳
    update task set version =  #{version} + 1 where version = #{version};
    

    CAS 底层原理

    从内存模型角度去理解,假设线程A和线程B都取到了CL值,A修改值之后,对比主内存的值和缓存副本原值,如果相同则认为没有人刷新过内存,则将修改过的新值刷新到主内存中;而B修改之后,对比发现原来的值与主内存的值不相同,则不会将新值刷新到主内存。失败的线程不会挂起,仅是被告知失败,并且允许再次尝试,当然也允许实现的线程放弃操作。有点类似于Git的操作,本地随意修改,但是提交到远程分支时,如果和原理的内容不一样就会有冲突,所以每次提交代码前会更新一下,也就是重新刷一下内存。

    偏移量的概念:内存中物理地址就是基地址x16+偏移地址,比如 0BAC:0100,0BAC是基地址,0100是偏移地址

    Unsafe类使用

    Unsafe类可以手动管理内存

    //package sun.misc;
    public final class Unsafe {
        private static final Unsafe theUnsafe;
        // 构造函数是private的,不允许外部实例化
        private Unsafe() {
        }
        
        static {
            theUnsafe=new Unsafe();
        }
        ...
    }
    

    通过反射获取私有字段的属性值

    public class LoanVo {
        private static final String name;
    
        static {
            name = "nihao";
        }
    }
    
        Field field = LoanVo.class.getDeclaredFields()[0];
        field.setAccessible(true);
        Object o = field.get(new LoanVo());
        System.out.println(o);//nihao
    

    获取Unsafe实例并进行操作

    Field field = Unsafe.class.getDeclaredFields()[0];
    //在访问时会忽略私有修饰符的检查
    field.setAccessible(true);
    Unsafe unsafe = (Unsafe) field.get(null);
    //对象操作
    LoanVo loanVo = (LoanVo) unsafe.allocateInstance(LoanVo.class);
    loanVo.setName("loan");
    System.out.println(loanVo);//LoanVo(name=loan)
    

    偏移量:字段内存的地址相对于对象内存地址的偏移量

    操作对象:objectFieldOffset

    //name字段在内存中的地址相对于对象内存地址的偏移量
    Field name = LoanVo.class.getDeclaredField("name");
    long fieldOffset = unsafe.objectFieldOffset(name);
    System.out.println(fieldOffset); //16
    //读写一个Object属性
    System.out.println(unsafe.getObject(loanVo,fieldOffset));//loan
    unsafe.putObject(loanVo,fieldOffset,"newValue");
    System.out.println(loanVo);//LoanVo(name=newValue)
    

    动态创建类:defineClass

    String classPath = "F:\\Program Files\\UnsafeTest.class";
    File file = new File(classPath);
    FileInputStream classIn = new FileInputStream(file);
    byte[] buffer = new byte[(int) file.length()];
    classIn.read(buffer);
    Class<?> aClass = unsafe.defineClass(null, buffer, 0, buffer.length, null, null);
    System.out.println(aClass);//class cn.mytest.samplemq.service.UnsafeTest
    

    内存操作:

    可以在Java内存区域中分配内存(allocateMemory),初始化内存(setMemory),清除内存( freeMemory)

    //直接脱离jvm,gc将无法管理以下方式申请的内存,一定要手动释放内存,避免内存溢出
    //申请一个大小为8字节的内存
    long memory = unsafe.allocateMemory(8);//地址为33333530
    //像内存中设置值为1
    unsafe.putInt(memory,(byte)1);
    System.out.println(unsafe.getInt(memory)); //1
    //直接获取内存的值 getByte(long addr)/putByte(long addr,byte value)
    //和map类型的操作
    byte value = unsafe.getByte(memory); // 1
    
    unsafe.putByte(memory,(byte) 3);
    System.out.println(unsafe.getByte(memory));//3
    
    //继续向地址偏移出设置非基本类型的值
    //putXXX(XXX ,long , XXX),第一个参数相当于作用域,第二个参数就是,第三个参数就是设置的值
    LoanVo loanVo = new LoanVo();
    loanVo.setName("haha");
    //比如unsafe.putObject(loanVo,fieldOffset,"newValue") 相当于 unsafe.setName("newValue")
    //设置的是偏移量内存的值,而下面这个相当于对当前地址的内存设值
    unsafe.putObject(LoanVo.class,memory+2,loanVo);
    System.out.println(unsafe.getObject(LoanVo.class,memory+2));//LoanVo(name=haha)
    
    unsafe.freeMemory(memory);
    System.out.println(unsafe.getByte(memory));
    

    Unsafe的CAS操作

    unsafe有很多操作暂时没有去了解,因为最重要的是CAS操作;cas方法都有4个参数:第一个是需要更新的作用域,第二个参数是需要比较的内存地址,第三个拿去跟地址内的值进行比较的期望值,第四个参数就是如果期望值和地址内的值相同则将这个值更新设置为新内存值

    //包含了compareAndSwapInt、compareAndSwapLong、compareAndSwapObject三个方法
    public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
    
    //还是申请一个内存,获取其地址
    long memory = unsafe.allocateMemory(2);
    LoanVo loanVo = new LoanVo();
    loanVo.setName("loan");
    //缩小一下域的范围
    Field name = LoanVo.class.getDeclaredField("name");
    //获取字段偏移地址
    long fieldOffset = unsafe.objectFieldOffset(name);
    boolean c = unsafe.compareAndSwapObject(loanVo, fieldOffset, "name", "dog fuc");
    boolean d = unsafe.compareAndSwapObject(loanVo, fieldOffset, loanVo.getName(), "dog fuc");
    System.out.println(unsafe.getObject(loanVo,fieldOffset)); //dog fuc
    

    CAS机制的缺点

    cas是一个非阻塞的轻量级锁,采用自旋锁进行CAS拿锁,当线程竞争激烈时长时间自旋不成功则会占用cpu较高,在JUC中就加了限制CAS次数

    只能保证一个共享变量原子操作,如果对多个变量操作,可以加锁来解决或者封装成对象类解决

    ABA问题:可能会造成其他线程修改过多次之后,原值却不变的情况,比如先修改为1,然后修改为2,又修改为1,解决的方式就是增加一个版本号,每次修改都会加一个版本,其实完全不用进行CAS,直接比对版本号也行!

    Atomic

    atomic:原子的,所以这个包提供原子变量的类

    借助硬件的相关指令来实现,不会阻塞线程(或者说只是在硬件级别上阻塞了)。可以对基本数据、数组中的基本数据、对类中的基本数据进行操作。原子变量类相当于一种泛化的volatile变量,能够支持原子的和有条件的读-改-写操作。

    可以分成4组:

    标量类(Scalar):AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference

    数组类:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray

    更新器类:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater

    复合变量类:AtomicMarkableReference,AtomicStampedReference

    标量类用来处理布尔,整数,长整数,对象四种数据,实现方式CAS + volatile,从而避免了synchronized的高开销。

    以AotomicInteger为例,先获取内存操作的Unsafe实例,通过objectFieldOffset获取字段value的valueOffset偏移量,对变量属性进行读写

    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;
    
    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicReference.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }
    //
    private volatile int value;
    

    底层以CAS方式去自旋设值

    public final int getAndSet(int newValue) {
        return unsafe.getAndSetInt(this, valueOffset, newValue);
    }
    
    public final int getAndAdd(int delta) {
    	return unsafe.getAndAddInt(this, valueOffset, delta);
    }
    

    举个实例:getAndIncrement()类似于i++

    AtomicInteger atomicInteger = new AtomicInteger();
    atomicInteger.getAndSet(10);
    
    new Thread(()->{
        for (int i=0;i<10;i++) {
            int andIncrement = atomicInteger.getAndIncrement();
            System.out.println(andIncrement);
        }
    }).start();
    new Thread(()->{
        for (int i=0;i<10;i++) {
            int andIncrement = atomicInteger.getAndIncrement();
            System.out.println(andIncrement);
        }
    }).start();
    new Thread(()->{
        for (int i=0;i<10;i++) {
            int andIncrement = atomicInteger.getAndIncrement();
            System.out.println(andIncrement);
        }
    }).start();
    

    AQS (AbstractQueuedSynchronizer)

    抽象队列式的同步器提供了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的ReentrantLock/Semaphore/CountDownLatch

    同步状态:AQS中维持一个全局的int状态码(state),线程通过修改(加/减指定的数量)码是否成功来决定当前线程是否成功获取到同步状态。

    //state是AQS的核心,因为AQS队列就是用于维护这个值
    private volatile int state;
    //用于指向-->队列中的头结点和尾部节点
    private transient volatile Node head;
    private transient volatile Node tail;
    

    同步队列:先进先出(FIFO)的双向队列,每个节点Node就代表一个线程,节点中又会有两个指针去指向前驱节点和后继节点,同步器Head指向获取同步状态的的第一个节点,Tail指向最后一个节点

    static final class Node {
        //前驱节点
        volatile Node prev;
        //后继节点
        volatile Node next;
        //当前线程
        volatile Thread thread;
        //封装新的node节点
        Node(Thread thread, Node mode) {
            //mode指节点类型,共享锁或独占锁
            this.nextWaiter = mode;
            this.thread = thread;
        }
    }
    

    独占锁和共享锁

    AQS支持两种获取同步状态的模式既独占式和共享式。独占式模式同一时刻只允许一个线程获取同步状态,而共享模式则允许多个线程同时获取。ReentrantLock就是以独占方式实现的互斥锁,ReadWriteLock读锁是共享锁,写锁是独占锁,Semaphore是一种共享锁

    static final class Node {
        static final Node SHARED = new Node();
        static final Node EXCLUSIVE = null;
    }
    

    以ReentrantReadWriteLock为例,AQS定义了独占模式的acquire()和release()方法,共享模式的acquireShared()和releaseShared()方法.

    public static class ReadLock implements Lock {
    
        public void lock() {
            //addWaiter(Node.SHARED),设置节点为共享状态
            sync.acquireShared(1);
        }
        
        public void unlock() {
            sync.releaseShared(1);
        }
    }
    
    public static class WriteLock implements Lock {
    
        public void lock() {
            //addWaiter(Node.EXCLUSIVE),设置节点为独占状态
            sync.acquire(1);
        }
        
        public void unlock() {
            sync.release(1);
        }
    }
    

    公平锁和非公平锁

    公平锁是指多个线程按照申请锁的顺序来获取锁。初始化时, state=0,表示无人占锁。A线程请求锁并取得了锁,把 state原子性+1,这时候state被改为1,A线程继续执行其他任务,然后线程B请求锁,无法获取锁,生成节点进行排队。初始化的时候,会生成一个空的头节点,然后才是B线程节点,这时候,如果线程A又请求锁,不需要排队,把状态值进行累加。如果线程A释放了一次锁仅仅是把状态值减了,只有线程A把此锁全部释放了,状态值减到0了,其他线程才有机会获取锁。当A把锁完全释放后,state恢复为0,然后会通知队列唤醒B线程节点,使B可以再次竞争锁。当然,如果B线程后面还有C线程,C线程继续休眠,除非B执行完了,通知了C线程。当一个线程节点被唤醒然后取得了锁,对应节点会从队列中删除。

    非公平锁简单来说就是不需要排队,当线程A执行完之后,要唤醒线程B是需要时间的,而且线程B醒来后还要再次竞争锁,所以如果在切换过程当中,来了一个线程C,那么线程C是有可能获取到锁的,如果C获取到了锁,B就只能继续休眠了。

    ReetrantLock而言,通过构造函数指定该锁是否是公平锁,默认是非公平锁

    //指定锁类型
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
    //默认为非公平锁
    public ReentrantLock() {
        sync = new NonfairSync();
    }
    //实现公平锁
    private ReentrantLock reentrantLock = new ReentrantLock(true);
    

    Synchronized而言,是一种非公平锁。由于其并不像ReentrantLock是通过AQS的来实现线程调度,所以并没有任何办法使其变成公平锁。

    以ReentrantLock为例,竞争锁和释放锁的实现

    ReentrantLock lock = new ReentrantLock();
    lock.lock();
    lock.unlock();
    
    --FairSync
    //公平锁中竞争锁
    final void lock() {
    	//都按照FIFO来竞争锁
        acquire(1);
    }
    
    --FairSync
    //非公平锁中竞争锁
    final void lock() {
    ·	//任何线程都可以先通过CAS去抢占锁
        if (compareAndSetState(0, 1))
        	setExclusiveOwnerThread(Thread.currentThread());
        else
        	//抢占失败,按照FIFO来竞争锁
        	acquire(1);
    }
    
    --AbstractQueuedSynchronizer
    //释放锁就一种实现
    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
            	unparkSuccessor(h);
            return true;
        }
        return false;
    }
    

    可重入机制

    synchronized可重入,每个实例对象内存都有一个对象头的区域,存放锁的信息(拿到锁的线程),以此来判断是否可重入。以下面连个方法为例,synchronized作用于myClass实例对象,所以访问method2时可重入。

    class MyClass {
        public synchronized void method1() {
            method2();
        }
          
        public synchronized void method2() {
              
        }
    }
    

    AQS的Lock锁也是可重入的,synchronized自动获取锁和释放锁,而Lock机制就是手动操作

    以ReentrantLock为例state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其他线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),获取多少次就要释放多少次才能保证state是能回到零态的。

    AQS的应用

    ReentrantLock独享锁和ReentrantReadWriteLock读写锁,Semaphore和CountDownLatch并发工具

    Lock(锁)的实现:

    void lock();//线程去竞争锁,拿不到锁就堵塞
    void lockInterruptibly();//也是竞争锁会阻塞,但是可中断
    boolean tryLock();//非阻塞去竞争锁,拿不到就返回false
    boolean tryLock(long time, TimeUnit unit)//尝试获取锁,如果超时还没拿到就返回false
    void unlock();//释放锁
    

    ReentrantLock的使用场景

    ReentrantLock常用的api:

    //查看有多少线程等待锁
    int getQueueLength()
    //是否有线程等待抢锁
    boolean hasQueuedThreads()
    //是否有指定线程等待抢锁
    boolean hasQueuedThread(Thread thread)
    //当前线程是否抢到锁。返回0代表没有
    int getHoldCount()
    //查询此锁是否由任何线程持有
    boolean isLocked()
    //是否为公平锁
    boolean isFair() 
    

    简单应用:类似于synchronized,线程会堵塞,直到拿到锁

    //设置为true时是公平锁,thread0->thread10按顺序拿到锁
    //默认非公平锁时则每一个线程都会试图去拿锁
    static ReentrantLock reentrantLock = new ReentrantLock(true);
    
    private static void map()  {
        System.out.println("map->"+Thread.currentThread().getName());
        reentrantLock.lock();
        try {
            System.out.println("lock->"+Thread.currentThread().getName());
            TimeUnit.SECONDS.sleep(2);
        }catch (Exception e) {
            e.printStackTrace();
        }finally {
            reentrantLock.unlock();
        }
    }
    
    public static void main(String[] args) {
        for (int i = 0; i <10; i++) {
            new Thread(()->{
                map();
            }).start();
        }
    }
    

    防止等待过长造成线程过多从而会导致内存溢出,需要设置一个等待超时时间

    static ReentrantLock reentrantLock = new ReentrantLock(true);
    
    private static void map()  {
        System.out.println("map->"+Thread.currentThread().getName());
        try {
            //队列线程等待10s之后没有拿到锁就中断队列
            if (reentrantLock.tryLock(10,TimeUnit.SECONDS)) {
                System.out.println("lock->"+Thread.currentThread().getName());
                TimeUnit.SECONDS.sleep(2);
            }
        }catch (Exception e) {
            
        }finally {
            //返回非0就表示当前线程拿到锁,所以需要释放
            if (reentrantLock.getHoldCount()>0) {
                reentrantLock.unlock();
                System.out.println("unlock->"+Thread.currentThread().getName());
            }else {
                //没有拿到锁的自然就是等待超时的线程
                System.out.println("超时->"+Thread.currentThread().getName());
            }
        }
    }
    
    public static void main(String[] args) {
        for (int i = 0; i <20; i++) {
            new Thread(()->{
                try {
                    map();
                }catch (IllegalMonitorStateException e) {
                    //如果没有拿到锁而去释放锁就会抛这个异常
                    System.out.println("IllegalMonitorStateException->"+Thread.currentThread().getName());
                }
            }).start();
            TimeUnit.SECONDS.sleep(1);
        }
    }
    

    ReentrantReadWriteLock

    ReentrantLock的升级版,提供了读锁和写锁,读锁为共享锁可以并发同时调用,写锁和ReentrantLock没区别,简单来说,涉及到数据更改的情况就会互斥

    private static ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
    private static ReadLock readLock = lock.readLock();
    //写锁和ReentrantLock的应用几乎一致
    private static WriteLock writeLock = lock.writeLock();
    
    private static void map() throws InterruptedException {
        try {
            readLock.lock();
            System.out.println("抢占读锁->"+Thread.currentThread().getName());
            TimeUnit.SECONDS.sleep(1);
        }finally {
            readLock.unlock();
            System.out.println("释放读锁->"+Thread.currentThread().getName());
        }
        TimeUnit.SECONDS.sleep(2);
        System.out.println("##############");
        try {
            writeLock.lock();
            System.out.println("抢占写锁->"+Thread.currentThread().getName());
            TimeUnit.SECONDS.sleep(1);
        }finally {
            writeLock.unlock();
            System.out.println("释放写锁->"+Thread.currentThread().getName());
        }
    }
    
    public static void main(String[] args) throws Exception {
        for (int i = 0; i <3; i++) {
            new Thread(()->{
                try {
                    map();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
    

    输出的结果一目了然

    抢占读锁->Thread-0
    抢占读锁->Thread-1
    抢占读锁->Thread-2
    释放读锁->Thread-1
    释放读锁->Thread-0
    释放读锁->Thread-2
    ##############
    ##############
    ##############
    抢占写锁->Thread-0
    释放写锁->Thread-0
    抢占写锁->Thread-2
    释放写锁->Thread-2
    抢占写锁->Thread-1
    释放写锁->Thread-1
    

    CountDownLatch

    线程计数器,设置计数器的初始值,当执行完countDown计数器的值就-1,当计数器的值为0时,在闭锁上等待的线程就可以恢复工作了

    //参数count为计数值
    CountDownLatch(int count)
    //调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
    void await();   
    //和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行
    boolean await(long timeout, TimeUnit unit);  
    //将count值减1
    void countDown();  
    

    主线程等待子线程执行完设置的CountDownLatch值才会继续往下执行

    final CountDownLatch countDownLatch = new CountDownLatch(3);
    for (int i = 0; i <100; i++) {
        new Thread(()->{
            System.out.println(Thread.currentThread().getName());
            countDownLatch.countDown();
            System.out.println(countDownLatch.getCount());
        }).start();
    }
    
    System.out.println(Thread.currentThread().getName());
    try {
        countDownLatch.await();
        //执行完3次countDownLatch.countDown()才会往下
        System.out.println("开始");
    }catch (InterruptedException e) {
        e.printStackTrace();
    }
    

    分成三个阶段,第一阶段所有线程准备好,当准备好之后,主线程裁判宣布开始,子线程开始出发,当所有子线程跑完,宣布结束!

    public static void main(String[] args) throws Exception {
        final CountDownLatch  judger = new CountDownLatch(1);
        final CountDownLatch player = new CountDownLatch(4);
        final CountDownLatch ready = new CountDownLatch(4);
        for (int i = 0; i <4; i++) {
            new Thread(()->{
                System.out.println("选手" + Thread.currentThread().getName() + "准备好");
                ready.countDown();
                try {
                    judger.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("选手" + Thread.currentThread().getName() + "出发!");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("选手" + Thread.currentThread().getName() + "到达终点");
                player.countDown();
            }).start();
            TimeUnit.SECONDS.sleep(2);
        }
    
        try {
            ready.await();
            System.out.println("运动员已经准备好"+Thread.currentThread().getName()+"裁判即将发布口令");
            judger.countDown();
            System.out.println("裁判"+Thread.currentThread().getName()+"已发送口令,正在等待所有选手到达终点");
            player.await();
            System.out.println("所有选手都到达终点");
        }catch (InterruptedException e) {
            e.printStackTrace();
        }
    
    }
    

    Semaphore控制并发数

    Semaphore信号量,Semaphore将AQS的同步状态用于保存当前可用许可的数量
    类似于ReentrantLock和CountDownLatch的结合,控制每次获取锁的线程数

    //设置只允许同时有三个线程可以拿到锁
    Semaphore semaphore = new Semaphore(3, true);
    

    第一个参数permit初始化可以同时并发的线程,每次acquire之后,status减1,减到0时所有线程阻塞,
    直到release释放锁status加1

        //初始化只允许3个线程同时拿到锁,设置公平锁
        final Semaphore semaphore = new Semaphore(3, true);
        //自定义阻塞队列线程池
        for (int i = 0; i < 10; i++) {
          TimeUnit.SECONDS.sleep(1);
    
          new Thread(()->{
            System.out.println(Thread.currentThread().getName()+":进入队列");
            try {
              //线程开始获取锁,拿到锁之后status减1,当status减到0时其他队列进入AQS排队
              semaphore.acquire();
              System.out.println(Thread.currentThread().getName()+":获取了锁");
              TimeUnit.SECONDS.sleep(5);
              //释放锁,status加1
              semaphore.release();
              System.out.println(Thread.currentThread().getName()+":释放了锁");
            } catch (InterruptedException e) {
              e.printStackTrace();
            }finally {
              System.out.println(Thread.currentThread().getName()+":此时AQS队列个数:"+semaphore.getQueueLength());
            }
          }).start();
        }
    

    分析返回结果,前三个队列顺利拿到锁,Thread-3和4进入队列排队,Thread-0释放了锁,Thread-3获取了锁,此时队列只剩T4;

    Thread-0:进入队列
    Thread-0:获取了锁
    Thread-1:进入队列
    Thread-1:获取了锁
    Thread-2:进入队列
    Thread-2:获取了锁
    Thread-3:进入队列
    Thread-4:进入队列
    Thread-0:释放了锁
    Thread-3:获取了锁
    Thread-0:此时AQS队列个数:1
    Thread-5:进入队列
    Thread-1:释放了锁
    Thread-4:获取了锁
    Thread-1:此时AQS队列个数:1
    Thread-6:进入队列
    Thread-2:释放了锁
    Thread-2:此时AQS队列个数:1
    Thread-5:获取了锁
    Thread-7:进入队列
    Thread-8:进入队列
    Thread-9:进入队列
    Thread-3:释放了锁
    Thread-3:此时AQS队列个数:3
    Thread-6:获取了锁
    Thread-4:释放了锁
    Thread-4:此时AQS队列个数:2
    Thread-7:获取了锁
    .......
    

    通过源码分析AQS

    主要还是通过ReetranLock,CountDownLatch,Semaphore,ReentranReadWriteLock常用api进行源码分析

    ReentranLock源码

    获取锁acquire

    公平锁/非公平锁实例获取锁

    --NonfairSync
    final void lock() {
        //非公平锁先通过CAS去竞争锁
        if (compareAndSetState(0, 1))
            //当非公平锁竞争成功之后该线程占用了独占锁
            //相对Node节点来说,并没有改变原有的AQS队列,只是更换了head节点的持有线程
            //简单来说就是两个男人决斗,赢了别人就取代了他的房子和女人
            setExclusiveOwnerThread(Thread.currentThread());
        else
        	//公平锁则按照队列顺序进行竞争锁
            acquire(1);
    }
    
    --AbstractQueuedSynchronizer
    protected final boolean compareAndSetState(int expect, int update) {
        //期望值status为0,也就是锁没有被持有则将Status更改为1,获取锁成功返回true
    	return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }
    --AbstractQueuedSynchronizer
    //初始化时就加载获取了偏移量
    static {
        stateOffset = unsafe.objectFieldOffset
            (AbstractQueuedSynchronizer.class.getDeclaredField("state"));
        headOffset = unsafe.objectFieldOffset
            (AbstractQueuedSynchronizer.class.getDeclaredField("head"));
        tailOffset = unsafe.objectFieldOffset
            (AbstractQueuedSynchronizer.class.getDeclaredField("tail"));
        waitStatusOffset = unsafe.objectFieldOffset
            (Node.class.getDeclaredField("waitStatus"));
        nextOffset = unsafe.objectFieldOffset
            (Node.class.getDeclaredField("next"));
    }
    

    acquire获取锁

    public final void acquire(int arg) {
        //tryAcquire可以理解为:尝试直接获取独占锁
        if (!tryAcquire(arg) &&
            //tryAcquire失败之后将当前线程封装Node添加到AQS队列尾部,并继续循环获取锁
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    

    tryAcquire非公平锁的实现:

    --Sync
    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        //获取status值,如果为0则表示仍然可以再CAS
        if (c == 0) {
            if (compareAndSetState(0, acquires)) {
            	//设置当前持有锁的线程
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        //status不为0,继续判断获取锁的线程是否为当前线程
        else if (current == getExclusiveOwnerThread()) {
        	//如果是则status加1,增加重入次数
            int nextc = c + acquires;
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        //如果都不是则返回false
        return false;
    }
    

    tryAcquire公平锁的实现:

    --FairSync
    protected final boolean tryAcquire(int acquires) {
    	...
        if (c == 0) {
        	//判断当前Node有没有前驱节点,有则需要继续进行CAS
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }	...
    }
    

    返回false,当前线程才能去CAS,目前理解有队列三种情况会返回false:1.当线程第一次尝试获取锁时,tail和head节点都是null,所以直接返回false去获取锁;2.只有一个线程在队列排队,此时status为0返回false; 3.有多个线程在队列中,第一个head节点已经释放了锁,等待next节点去获取锁,所以只需判断next节点是不是当前线程的节点

    --AbstractQueuedSynchronizer
    public final boolean hasQueuedPredecessors() {
        Node t = tail;
        Node h = head;
        Node s;
        //如果head和tail都是null,那么也是返回false,就不存在h.next空指针
        return h != t &&
        //h != t成立,锁已经释放,等待后继节点去获取,判断是否为当前线程
        ((s = h.next) == null || s.thread != Thread.currentThread());
    }
    

    当第一个A线程tryAcquire直接拿到锁,队列没有阻塞的线程;第二个线程B也去tryAcquire,如果status为0则和前面一样,不为0则添加到队列的中去,因为队列初始化的head和tail节点都是空节点,线程进行自旋等待线程A释放,当线程A释放之后,通过自旋线程B第二次tryAcquire,因为status释放之后为0,则进行hasQueuedPredecessors从而获取了锁;第三个线程C在tryAcquire时,如果status为0则表示线程B已经释放了,线程C也同上步骤拿到了锁,并且此时的head节点还是线程B,除非线程B没有释放,线程C也加入到AQS队列中,此时队列长度为2;如果之后线程D,E,F等等依次进行tryAcquire,只要B没有释放,那么都按顺序加入到队列中;直到线程B释放之后,线程C通过自旋判定出自己排在head的next之后再tryAcquire发现status=0从而获得锁,设置线程C的节点为Head并清除线程B的节点内存,依次类推!

    尝试获取锁失败后将失败的线程封装之后添加到AQS队列中

    封装Node节点:添加节点并处理队列结构,线程第一次添加队列时首先会初始化一个new Node()节点作为head和tail,然后线程节点取代这个初始化节点作为tail节点

    private Node addWaiter(Node mode) {
        //this.nextWaiter = mode; 暂且把nextWaiter理解为锁的模式为EXCLUSIVE共享锁
        Node node = new Node(Thread.currentThread(), mode);
       	//将当前节点的前驱节点设置为原tail节点
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                //将原tail节点的后继设置为当前节点
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }
    
    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            //tail节点为null,队列是空的
            if (t == null) { // Must initialize
                //初始化一个队列设置为head和tail节点
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                //当循环之后,tail不为空,已经初始化了
                node.prev = t;
                //设置当前节点为tail节点
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }
    

    acquireQueued抢锁:tryAcquire尝试获取锁失败之后,设置前驱节点为SIGNAL状态表示前驱节点释放锁的时候会唤醒后继节点,设置成功之后会将当前线程park,类似于wait,等待unpark唤醒

    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                //先去获取当前节点的prev(前驱)节点
                final Node p = node.predecessor();
                //前驱节点是head则再次尝试获取锁
                if (p == head && tryAcquire(arg)) {
                    //获取锁之后设置head为当前node并清空node的线程信息和prev
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                //获取锁失败判断是否需要挂起
                if (shouldParkAfterFailedAcquire(p, node) &&
                    //前驱节点状态为SIGNAL,当前线程park阻塞
                    //LockSupport.park/unpark为线程提供一个类似开关的功能
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                //取消获取锁,主要就是设置节点waitStatus为cancelled,清空节点内容
                //最主要的是unpark为下一个线程放行
                cancelAcquire(node);
        }
    }
    

    shouldParkAfterFailedAcquire,设置前驱节点的状态为SIGNAL表示当前线程被阻塞或者即将被阻塞,那么前驱线程释放锁或者取消后需要唤醒后继线程

    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        //前驱节点状态waitStatus默认为0(int 默认值)
        int ws = pred.waitStatus;
        //SIGNAL值为-1表示后继节点被阻塞需要唤醒
        if (ws == Node.SIGNAL)
            return true;
        //大于0的状态只有一个cancelled取消
        if (ws > 0) {
            do {
                //取消就是将前驱节点从队列中删除,所以当前节点的prev就直接指向了上上一个节点
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            //作为原前驱节点的前驱节点,它的next就指向了当前节点
            pred.next = node;
        } else {
            //剩下就只有0和PROPAGATE(可以表示共享模式则设置前驱节点为SIGNAL
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        //前驱节点不为SIGNAL则继续去循环获取锁
        return false;
    }
    

    LockSupport:针对单个线程的拦截和放行,阻塞维度是线程,和wait类似

    Thread thread = new Thread(() -> {
        System.out.println(LocalTime.now()+":start park:" + Thread.currentThread().getName());
        LockSupport.park();
        System.out.println(LocalTime.now()+":park1:" + Thread.currentThread().getName());
        LockSupport.park();//只有一个通行证,所以会阻塞
        System.out.println(LocalTime.now()+":park2:" + Thread.currentThread().getName());
    });
    thread.start();
    
    Thread thread2 = new Thread(() -> {
        System.out.println(LocalTime.now()+":start park:" + Thread.currentThread().getName());
        LockSupport.park();
        System.out.println(LocalTime.now()+":park1:" + Thread.currentThread().getName());
        LockSupport.park();//只有一个通行证,所以会阻塞
        System.out.println(LocalTime.now()+":park2:" + Thread.currentThread().getName());
    });
    thread2.start();
    
    TimeUnit.SECONDS.sleep(5);
    LockSupport.unpark(thread);
    LockSupport.unpark(thread2);
    TimeUnit.SECONDS.sleep(5);
    LockSupport.unpark(thread);
    
    21:05:05.766:start park:Thread-0
    21:05:05.766:start park:Thread-1
    21:05:10.749:park1:Thread-0
    21:05:10.749:park1:Thread-1
    21:05:15.749:park2:Thread-0
    

    lockInterruptibly可中断加锁

    lock方法会忽略中断请求,继续获取锁直到成功,而lockInterruptibly则直接抛出中断异常来立即响应中断,
    一旦检测到中断请求,被中断线程立即返回不再参与锁的竞争并且取消锁获取操作(interrupt()操作,虽然只是恢复了中断状态为true,但是也会立即退出队列)

    ReentrantLock lock = new ReentrantLock();
    new Thread(()->{
        try {
            lock.lockInterruptibly();
            System.out.println(Thread.currentThread().getName()+":抢到锁");
            TimeUnit.SECONDS.sleep(3);
            lock.unlock();
            System.out.println(Thread.currentThread().getName()+":释放锁");
        } catch (InterruptedException e) {}
    }).start();
    
    Thread thread = new Thread(() -> {
        try {
            System.out.println(Thread.currentThread().getName() + ":进入队列");
            lock.lockInterruptibly();
            System.out.println(Thread.currentThread().getName() + ":抢到锁");
            lock.unlock();
        } catch (InterruptedException e) {
            System.out.println(Thread.currentThread().getName() + ":没有抢到锁");
        }
    });
    TimeUnit.SECONDS.sleep(2);
    thread.start();
    //interrupt即使只是一个中断信号,但是会立即抛出异常
    thread.interrupt();
    

    释放锁release

    tryRelease先设置status减1,如果status==0则认为释放锁

    public final boolean release(int arg) {
        //释放锁才返回true,如果只是重入锁减一则返回false
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                //释放当前Node之后unpark后继节点的线程
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
    
    protected final boolean tryRelease(int releases) {
        //先减一
        int c = getState() - releases;
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        boolean free = false;
        //如果status==0则表示释放锁,设置锁的持有线程为null并返回true
        if (c == 0) {
            free = true;
            setExclusiveOwnerThread(null);
        }
        setState(c);
        return free;
    }
    

    当前节点释放锁成功之后,后继线程还处于park,需要先unpark后继节点的线程

    private void unparkSuccessor(Node node) {
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);
        Node s = node.next;
        //waitStatus>0只有cancelled状态
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            //s.thread后继线程unpark
            LockSupport.unpark(s.thread);
    }
    

    Condition条件队列

    或者叫信号队列,await会将当前线程park(也就是释放了通行证锁)并添加到condition条件队列中,释放锁之后其他线程开始抢锁,知道被signalAll,将条件队列的线程唤醒并重新加入到AQS队列中竞争锁

    ReentrantLock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
    
    new Thread(()->{
        lock.lock();
        System.out.println(Thread.currentThread().getName()+":抢到锁");
        try {
            TimeUnit.SECONDS.sleep(2);
            //①释放锁,并进入睡眠等待唤醒
            condition.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //⑤抢到锁继续跑完任务
        System.out.println("重新抢到了锁:"+Thread.currentThread().getName());
        lock.unlock();
    },"await").start();
    
    new Thread(()->{
        try {
            //如果signal线程先抢到锁则会造成await线程无法被唤醒
            TimeUnit.SECONDS.sleep(2);
            //②await线程释放锁,该线程抢到锁
            lock.lock();
            System.out.println(Thread.currentThread().getName()+":抢到锁");
            TimeUnit.SECONDS.sleep(2);
            //③await已经休眠,condition唤醒所有休眠线程,回到AQS队列等待继续获取锁
            condition.signalAll();
            TimeUnit.SECONDS.sleep(2);
            System.out.println("继续完成任务:"+Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //④释放锁
        lock.unlock();
    },"signal").start();
    

    ReentrantReadWriteLock源码

    AQS队列中的节点其实是独占和共享节点并存的,共享锁是允许多个线程持有;持有写锁的线程可以继续获取读锁,反之不行。

    static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    static ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
    static ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
    
    readLock.lock();
    writeLock.lock();
    

    tryAcquireShared尝试获取共享锁

    独占模式和共享模式对于 state 的操作完全不一样,在共享模式下,每个线程都可以对 state 进行加减操作

    public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }
    
    protected final int tryAcquireShared(int unused) {
        Thread current = Thread.currentThread();
        int c = getState();
        //独享锁:status为0则exclusiveCount(c)为0
        //表示有独占锁且不是当前线程持有(当前线程持有写锁也可以继续获取读锁)
        if (exclusiveCount(c) != 0 &&
            getExclusiveOwnerThread() != current)
            //表示获取失败
            return -1;
        //读锁被获取的次数
        int r = sharedCount(c);
        //如果是公平锁则会hasQueuedPredecessors判断是否有前驱节点,也就是有人排队,否则返回false
        //如果非公平锁则会判断head的后继节点是否为共享模式,如果是共享则返回false
        if (!readerShouldBlock() &&
            //MAX_COUNT是低16位的最大值
            r < MAX_COUNT &&
            //SHARED_UNIT二进制的第17位,也就是共享锁加1,此时如果有写锁改变了c值也会导致写锁失败
            compareAndSetState(c, c + SHARED_UNIT)) {
            //次数为0表示读锁还没有获取过
            if (r == 0) {
                firstReader = current;
                firstReaderHoldCount = 1;
            } else if (firstReader == current) {
                //重入锁机制
                firstReaderHoldCount++;
            } else {
                //HoldCounter封装线程id和一个count值,记录当前线程持有的读锁数量
                HoldCounter rh = cachedHoldCounter;
                if (rh == null || rh.tid != getThreadId(current))
                    //cachedHoldCounter最后一个获取读锁的线程
                    cachedHoldCounter = rh = readHolds.get();
                else if (rh.count == 0)
                    readHolds.set(rh);
                rh.count++;
            }
            return 1;
        }
        //获取锁失败进行自旋
        //大致就是重新获取status值,再判断是否有写锁,有则返回-1,没有则再CAS去获取写锁
        return fullTryAcquireShared(current);
    }
    

    位运算:<<表示左移,>>>无符号右移(当为正数时和>>没区别)

    static final int SHARED_SHIFT   = 16;
    //1 0000 0000 0000 0000
    static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
    //1111 1111 1111 1111
    static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
    //右移16位,表明共享锁值在16位以上
    static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
    //&表示两个位都为1时,结果才为1,表明独享锁在16位以下
    static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
    

    status通过高16位和低16位可以区分读锁和写锁

    private void doAcquireShared(int arg) {
        //获取锁失败就加入到AQS队列中,这是个共享模式
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                //判断前驱节点是否是head
                if (p == head) {
                    //获取共享锁成功,唤醒后继节点进行自旋
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        //获取锁成功,设置当前节点为head,同时唤醒后继节点
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                //获取锁失败,设置前驱节点为SIGNAL,并park阻塞自己
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
    

    和独享锁不同,因为独享锁只允许一个线程访问,所以在释放锁的时候再去唤醒即可

    private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; 
        setHead(node);
    
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            if (s == null || s.isShared())
                //如果后继节点是共享模式则唤醒
                doReleaseShared();
        }
    }
    

    对于共享锁的释放,由于不阻塞其他线程获取读锁,所以释放锁主要是唤醒写锁的线程;也证明了读锁会阻塞写锁!

    public final boolean releaseShared(int arg) {
        //只有当共享锁和读写锁都释放之后才会返回true去唤醒后继节点
        if (tryReleaseShared(arg)) {
            //又由于后继是共享模式时不会在release时被唤醒,所以主要是用于唤醒后继节点是写锁的线程
            doReleaseShared();
            return true;
        }
        return false;
    }
    
    protected final boolean tryReleaseShared(int unused) {
        Thread current = Thread.currentThread();
        //也就是说当前线程是唯一个持有读锁的线程
        if (firstReader == current) {
            //重入机制
            if (firstReaderHoldCount == 1)
                firstReader = null;
            else
                firstReaderHoldCount--;
        } else {
            //cachedHoldCounter 是否缓存的是当前线程,不是的话要到 ThreadLocal 中取、
            //这也是cachedHoldCounter的作用,用于缓存
            HoldCounter rh = cachedHoldCounter;
            if (rh == null || rh.tid != getThreadId(current))
                rh = readHolds.get();
            int count = rh.count;
            //啰嗦,如果只有1说明没有重入了,直接释放了锁,所以当前readHolds移出ThreadLocal
            if (count <= 1) {
                readHolds.remove();
                if (count <= 0)
                    throw unmatchedUnlockException();
            }
            //大于1表示有重入,则减一即可
            --rh.count;
        }
        for (;;) {
            int c = getState();
            //SHARED_UNIT是17位为1,也就是共享数为1
            int nextc = c - SHARED_UNIT;
            if (compareAndSetState(c, nextc))
                //期望nextc值为0,只可能是没有任何线程持有锁才会返回true
                return nextc == 0;
        }
    }
    
    展开全文
  • 我是李福春,我在准备面试,今天的题目是:CAS和AQS是什么?答:CAS是一系列的操作集合,获取当前值进行计算,如果当前值没有改变,表示线程没有被占用,直接更新成功,否则,进行重试或者返回成功或者失败。 他是...
  • Java并发编程之CAS和AQS

    2021-01-28 22:51:51
    什么是CAS CAS(compare and swap),字面意思比较并交换,是解决多线程并行情况下使用锁造成性能损耗的一种机制. public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt...
  • JAVA并发编程: CAS和AQS

    2021-03-13 21:04:21
    说起JAVA并发编程,就不得不聊聊CAS(Compare And Swap)和AQS了(AbstractQueuedSynchronizer)。CAS(Compare And Swap)什么是CASCAS(Compare And Swap),即比较并交换。是解决多线程并行情况下使用锁造成性能损耗的一...
  • java.util.concurrent.locks.AbstractQueuedSynchronizer 抽象类,简称 AQS AQS共享资源的方式:独占式共享式 AQS默认提供了独占式共享式两种模式,JDK对应的实现有ReentrantLockReentrantReadWriteLock 独占...
  • 文章目录什么是CAS原理剖析CAS的使用ABA问题耗能问题实现计数器什么是AQSAQS实现原理AQS的使用 什么是CAS CAS的全称是Compare And Swap , 顾名思义就是比较交换 他是一个乐观锁, 是解决多线程并行情况下使用重量...
  • Java并发之CASAQS简介

    2021-01-15 11:07:58
    1,什么是CAS CAS(Compare And Swap),即比较并交换。是解决多线程并行情况下使用锁造成性能损耗的一种机制,CAS操作包含三个操作数——内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配...
  • 若V值E值不同,则说明已经有其他线程做了更新,则当前线程什么都不做。通俗的理解就是CAS操作需要我们提供一个期望值,当期望值与当前线程的变量值相同时,说明还没线程修改该值,当前线程可以进行修改...
  • 深入理解CASAQS

    2021-03-03 17:15:18
    CASAQS一、CAS1、什么是CAS2、CAS进行原子操作的三大问题3、循环CAS二、AQS1、核心思想2、具体实现3、AQS 对资源的两种共享方式4、AQS 的应用 一、CAS 1、什么是CAS 首先,CAS是一种算法,不是锁 CAS,其实是个...
  • Java中的CAS实现原理 什么是CAS? 在计算机科学中,比较交换(Conmpare And Swap)是用于实现多线程同步的原子指令。 它将内存位置的内容与给定值进行比较,只有在相同的情况下,将该内存位置的内容修改为新的...
  • CAS(Compare And Swap)原理分析 字面意思是比较交换,先看看下面场景(A B 线程同时执行下面的代码): int i = 10;...场景 1:A 线程执行代码 1 代码 2,然后 B 线程执行代码 1 代码 2,CAS
  • java cas 和AQS

    2021-06-15 15:35:17
    什么是CAS机制 CAS是英文单词Compare And Swap的缩写,翻译过来就是比较并替换。 CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。 更新一个变量的时候,只有当变量的预期值A内存地址...
  • 锁概述谈到并发,不得不谈ReentrantLock;而谈ReentrantLock,不得不谈AbstractQueuedSynchronizer(AQS)!类如其名,抽象的队列式的同步器...
  • 同步 实现方式 CAS Compare And Swap,CAS ...ABA问题,CompareAndSwap的值从A变为B,再由B变为A,这种情况下,CAS认为值没有变,但其实是变了的,需要使用版本号来解决,Atomic使用AtomicStampedReference来解决 CAS
  • 在Lock锁的理念中,采用的是一种乐观锁的形式,即多线程去修改共享资源时,不是在修改之前就加锁,而是乐观的认为没有别的线程自己争锁,就是通过CAS的理念去保障共享资源的安全性的。CAS的基本思想是,拿变量的...
  • CASAQS 的几点说明

    2021-06-18 08:26:24
    CAS CAS是一系列指令的集合,是java并发lock-free的主要基础代码。依赖于Unsafe本地对象来实现(CPU的特定指令去实现)。
  • 【腾讯阿里最全面试题】介绍下Synchronized、Volatile、CASAQS,以及各自的使用场景(文章较长,建议收藏观看) 相关视频讲解: 面试中出现概念最高的技术-原来就是这个锁 后台开发中必备技能—锁;原子操作 ...
  • 3、Java中的锁实现 3.1、队列同步器(AQS) 队列同步器AbstractQueuedSynchronizer(以下简称同步器),是用来构建锁或者其他同步组件的基础框架。 3.1.1、它使用了一个int成员变量表示同步状态。 3.1.2、通过内置...
  • 队列中的节点有两种模式:独占共享 AQS独占模式 int state > 0 代表锁被占用,只能被单个节点占用 = 0 代表锁被释放 独占模式下,锁只能被一个线程获取,其他线程必须等待。 源码分析 对于Node对象,他主要存储...
  • 2、锁实现的基本原理2.1、volatileJava编程语言允许线程访问共享变量, 为了确保共享变量能被准确一致地更新,线程应该确保通过排他锁单独获得这个变量。Java语言提供了volatile,在某些情况下比锁要更加方便。...
  • CAS,compareAndSwap,见名知意,比较交换。 CAS的全称为Compare And Swap,直译就是比较交换。是一条CPU的原子指令,其作用是让CPU先进行比较两个值是否相等,然后原子地更新某个位置的值,其实现方式是基于硬件...
  • } } 当调用lock方法时,会调用acquire(1)方法,获取state,state等于0表示没有线程获取到锁,接下来会判断是否有等待的线程,如果没有等待线程进行cas操作state成功,则设置当前独占线程为当前线程,返回true,...
  • CAS/AQS

    2020-12-23 16:55:10
    文章目录CAS原理实现CAS中得经典问题,ABA问题AQS原理(state资源状态计数)资源共享的方式实现方式 CAS CAS函数,是比较并交换函数,它是原子操作函数 原理 CAS是基于乐观锁的原理进行操作的。它总是认为自己可以成功...
  • CASAQS

    2021-04-01 16:52:03
    CAS(Compare And Set) Atomic原子类,无锁化CAS机制,通过CAS机制保证多线程修改一个数值的安全性。 发起CAS比较这个值有没有被修改过,这个CAS是原子的,不会被打断。如果值已经被修改了,导致CAS失败,失败后...
  • 1、Synchronized是JVM虚拟机实现的一种互斥同步的方式,被Synchronized修饰的程序块在编译后,生成了monitorentermonitorexit指令; 当虚拟机执行到monitorenter指令的时候,首先要尝试获取对象的锁。如果这个...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 27,523
精华内容 11,009
关键字:

cas和aqs

友情链接: 86623096.rar