JUC_AQS_相关

1. AbstractQueuedSynchronizer

1.0 AQS 和 其他同步组件的关系

1.1 LockSupport 的 park() 和 unpark()

1.2 AQS 的独占锁模式

  • 参考文献

    AQS_IT老哥

    CLH

  • AQS 独占锁你需要知道什么

    image-20200725162842230

  • 通过 ReentrantLock 讲解 AQS

1.3 AQS 总结

  • 死磕 java同步系列之AQS终篇(面试)

  • 简介

    AQS 为 Java 中几乎所有的锁 和 同步容器提供了一个基础框架

    三大件:

    状态变量 state

    • 互斥锁 : state : 0 => 1 获取锁了,可重入锁就是 将 state +1
    • 共享锁:
    • 互斥锁 + 共享锁:

    AQS 同步队列

    • 获取锁失败的线程会封装成 Node 进入这个队列, 等待锁释放后唤醒下一个排队的线程(互斥)

    Condition 条件队列

    • 用于实现条件锁,用于条件不成立时,线程park后存放的队列.
    • 当条件成立时,其他线程将 signal 这个队列中的元素,每次移动一个到 AQS 的同步队列中.等待占有锁的线程释放后唤醒. 不是立即唤醒
    • 经典应用: BlockingQueue. 当队列为空时, 阻塞在 notEmpty 条件上,一旦添加了一个元素,则通知 notEmpty 条件, 将其队列中的元素移动到AQS 队列中等待被唤醒.
  • 模板方法设计模式

    //获取独占锁(互斥锁)
    public final void acquire(int arg) {
        //tryAcquire(arg) 需要子类实现, 尝试获取锁
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    //获取独占锁可中断
    public final void acquireInterruptibly(int arg)
        throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
         //tryAcquire(arg) 需要子类实现
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }
    //释放共享锁
    public final boolean release(int arg) {
        //tryRelease(int arg) 需要子类实现  尝试释放锁
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
    //获取共享锁
    public final void acquireShared(int arg) {
        //tryAcquireShared(arg) 需要子类实现
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }
    //获取共享锁可中断
    public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        //tryAcquireShared(arg) 需要子类实现
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }
    //释放共享锁
    public final boolean releaseShared(int arg) {
        //tryReleaseShared(arg) 需要子类实现
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }
    
  • 借助 AQS 实现一个锁

    /**
     * @author yuan
     * @date 2020-07-28 20:58
     */
    public class MyLockBaseOnAQS {
    
        private static class Sync extends AbstractQueuedSynchronizer{
            @Override
            protected boolean tryAcquire(int arg) {
                if (compareAndSetState(0,1)){
                    setExclusiveOwnerThread(Thread.currentThread());
                    return true;
                }
                return false;
            }
    
            @Override
            protected boolean tryRelease(int arg) {
                setExclusiveOwnerThread(null);
                setState(0);
                return true;
            }
        }
        private final Sync sync = new Sync();
    
        //加锁
        public void lock(){
            sync.acquire(1);
        }
    
        public void unlock(){
            sync.release(1);
        }
    
        private static int count = 0;
    
        public static void main(String[] args) throws InterruptedException {
            MyLockBaseOnAQS lock = new MyLockBaseOnAQS();
    
            CountDownLatch c = new CountDownLatch(1000);
    
            for (int i = 0; i < 1000; i++) {
                new Thread(()->{
                    long start = System.currentTimeMillis();
                    lock.lock();
                    try{
                        for (int j = 0; j < 1000; j++) {
                            count++;
                        }
                    }finally {
                        lock.unlock();
                    }
                    long end = System.currentTimeMillis();
                    System.out.println(Thread.currentThread().getName()+"--"+(end-start));
                    c.countDown();
                }).start();
            }
            c.await();
            System.out.println(count); //1000000
        }
    }
    

2. ReentrantLock

2.1 自己动手写一个锁

  • 来源: 死磕 java同步系列之自己动手写一个锁Lock

  • 需要的东西: 一个变量、一个队列、执行CAS/park/unpark的Unsafe类。

  • 流程图:

    img

  • 源码 74行___while (node.prev != head || !compareAndSetState(0,1))

    package cn.yuan.test.juc;
    
    import sun.misc.Unsafe;
    
    import java.lang.reflect.Field;
    import java.util.concurrent.CountDownLatch;
    
    /**
     * @author yuan
     * @date 2020-07-25 16:47
     */
    public class MyLock {
        /**
         * 手写一个锁需要哪些知识?
         *      完成最简单的功能:
         *          正常的加锁/解锁操作
         *      准备工作:
         *          使用 state 来表示加锁的状态
         *          需改 state 状态,涉及到多线程操作,使用 Unsafe 中的 CAS
         *          其他线程竞争失败需要排队,排队时候只能阻塞,等待当前持有锁的线程释放锁,在次唤醒去获取锁
         *              队列以及 park/unpark
         *
         */
        private static class Node{
            Thread thread;
            Node prev;
            Node next;
    
            public Node(){}
    
            public Node(Thread thread,Node prev){
                this.thread = thread;
                this.prev = prev;
            }
        }
    
        static final Node EMPTY = new Node();
    
        public MyLock(){
            head = tail = EMPTY;
        }
    
        private volatile int state;
        /**
         * head 初值 head = tail = new Node();
         * 然后都是当前正在持有锁的线程封装的 Node,
         * head.next 就是下一次要 unpark 的线程 , (node.prev != head,重要) 避免了有新的线程和刚唤醒挂起线程 争抢同一锁
         */
        private volatile Node head;
        private volatile Node tail;
    
        public void lock(){
            /**
             * 步骤:
             * (1)尝试获取锁,成功了就直接返回;
             * (2)未获取到锁,就进入队列排队;
             * (3)入队之后,再次尝试获取锁;
             * (4)如果不成功,就阻塞;
             * (5)如果成功了,就把头节点后移一位,并清空当前节点的内容,且与上一个节点断绝关系;
             * (6)加锁结束;
             *
             */
            if (compareAndSetState(0,1)){
                return;
            }
            //加锁失败
            //node 是当前线程封装成的 Node
            Node node = enqueue();
            Node prev = node.prev;
            System.out.println(Thread.currentThread().getName()+"-"+prev);
            //再次尝试获取锁,检测上一个结点是不是 head
            //只能上一个结点是 head ,当前线程才能尝试获取锁
            while (node.prev != head || !compareAndSetState(0,1)){
                //未获取到锁,阻塞
                unsafe.park(false,0L);
            }
    
            // 来到这里:  表示 获取到锁了 且上一个结点 是 head,当前 Node 要出队
            // head 后移一位
            System.out.println(Thread.currentThread().getName()+"-"+prev);
    
            head = node;
            node.thread = null;
            //将上一个结点从链表中剔除
            node.prev = null;
            prev.next = null;
        }
    
        private Node enqueue() {
            while (true){
                /**
                 * 1)获取尾结点
                 * 2)新结点插入到尾结点上
                 */
                Node t = tail;
                Node node = new Node(Thread.currentThread(),t);
                if (compareAndSetTail(t,node)){
                    //成功
                    t.next = node;
                    return node;
                }
            }
        }
    
        public void unlock() {
            // 不需要使用 CAS 更新, 因为只有一个线程能抢到锁, 释放锁也就只有一个线程,不存在并发 (这里的锁不是可重入的,且是非公平锁)
            /**
             *  (1)把state改成0,这里不需要CAS更新,因为现在还在加锁中,只有一个线程去更新,在这句之后就释放了锁;
             * (2)如果有下一个节点就唤醒它;
             * (3)唤醒之后就会接着走上面lock()方法的while循环再去尝试获取锁;
             * (4)唤醒的线程不是百分之百能获取到锁的,因为这里state更新成0的时候就解锁了,之后可能就有线程去尝试加锁了。
             */
            state = 0;
            Node next = head.next;
            if (next != null){
                unsafe.unpark(next.thread);
            }
        }
    
        /**
         * ====================================================================================
         * 摘自 AQS
         * 使用 魔法类中的方法获取当前 volatile 变量 以及调用 CAS 方法 封装自己的方法
         */
        private static final Unsafe unsafe;
        private static final long stateOffset;
        private static final long headOffset;
        private static final long tailOffset;
    
        static {
            try {
                Field f = Unsafe.class.getDeclaredField("theUnsafe");
                f.setAccessible(true);
                unsafe = (Unsafe) f.get(null);
    
                stateOffset = unsafe.objectFieldOffset
                        (MyLock.class.getDeclaredField("state"));
                headOffset = unsafe.objectFieldOffset
                        (MyLock.class.getDeclaredField("head"));
                tailOffset = unsafe.objectFieldOffset
                        (MyLock.class.getDeclaredField("tail"));
    
            } catch (Exception ex) { throw new Error(ex); }
        }
    
        private final boolean compareAndSetHead(Node update) {
            return unsafe.compareAndSwapObject(this, headOffset, null, update);
        }
    
        private final boolean compareAndSetTail(Node expect, Node update) {
            return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
        }
    
        protected final boolean compareAndSetState(int expect, int update) {
            // See below for intrinsics setup to support this
            return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
        }
    }
    
    
    class Test{
        static int count = 0;
    
        static MyLock lock = new MyLock();
    
        public static void addCount() {
            //MyLock lock = new MyLock(); //错误的写法
            lock.lock();
            try{
    //            try {
    //                TimeUnit.MILLISECONDS.sleep(100);
    //            } catch (InterruptedException e) {
    //                e.printStackTrace();
    //            }
                count++;
            } finally {
                lock.unlock();
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            CountDownLatch countDownLatch = new CountDownLatch(500);
            for (int i = 0; i < 500; i++) {
                new Thread(()->{
                    for (int j = 0; j < 100; j++) {
                        addCount();
                    }
                    countDownLatch.countDown();
                }).start();
            }
            countDownLatch.await();
            System.out.println(count);
        }
    }
    
  • 小结

    (1)自己动手写一个锁需要做准备:一个变量、一个队列、Unsafe类。

    (2)原子更新变量为1说明获得锁成功;

    (3)原子更新变量为1失败说明获得锁失败,进入队列排队;

    (4)更新队列尾节点的时候是多线程竞争的,所以要使用原子更新;

    (5)更新队列头节点的时候只有一个线程,不存在竞争,所以不需要使用原子更新;

    (6)队列节点中的前一个节点prev的使用很巧妙,没有它将很难实现一个锁,只有写过的人才明白,不信你试试^^( 我信了,看了半天 终于理解了) (通过判断前一个结点是否为 head 来判断当前结点是否是从队列中出队的那个Node,然后争抢锁)


    (7) 上面实现的锁不支持锁的重入

2.2 ReentrantLock(独占锁)

  • 实现了 Lock 接口

    public class ReentrantLock implements Lock
    Lock 接口的方法:
    // 获取锁
    void lock();
    // 获取锁(可中断)
    void lockInterruptibly() throws InterruptedException;
    // 尝试获取锁,如果没获取到锁,就返回false
    boolean tryLock();
    // 尝试获取锁,如果没获取到锁,就等待一段时间,这段时间内还没获取到锁就返回false
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    // 释放锁
    void unlock();
    // 条件锁
    Condition newCondition();
    
  • 内部类

    Sync、NonfairSync、FairSync
    (1)抽象类 Sync 实现了AQS的部分方法;
    (2)NonfairSync实现了Sync,主要用于非公平锁的获取;
    (3)FairSync实现了Sync,主要用于公平锁的获取。

  • 构造方法

    //默认使用非公平锁
    public ReentrantLock() {
        sync = new NonfairSync();
    }
    // fair 为 true => 公平锁
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
    

2.3 独占锁 & 公平锁 [lock,unlock]

  • lock( ) 方法的调用链

    image-20200726095209774

    image-20200726161236020

    • cancelAcquire: 当执行 acquireQueued 自旋争抢锁的过程中发生异常,会执行 cancelAcquire 方法.
    • acquireQueued : 负责将入队的 Node park 以及被 unpark 之后继续争抢锁,还有发生异常导致一直抢不到锁 ,执行上面的 cancelAcquire 方法
    • shouldParkAfterFailedAcquire: 将当前结点的前驱结点的 waitStatus 修改为 -1. 默认情况下为0,然后通过 CAS 0 => -1.如果存在 waitStatus 的结点即 cancelled, 清除出队
  • 大致过程:

    (1)尝试获取锁,如果获取到了就直接返回了;

    (2)尝试获取锁失败,再调用addWaiter()构建新节点并把新节点入队;

    (3)然后调用acquireQueued()再次尝试获取锁,如果成功了,直接返回;

    (4)如果再次失败,再调用shouldParkAfterFailedAcquire()将节点的等待状态置为等待唤醒(SIGNAL);

    (5)调用parkAndCheckInterrupt()阻塞当前线程;

    (6)如果被唤醒了,会继续在acquireQueued()的for()循环再次尝试获取锁,如果成功了就返回;

    (7)如果不成功,再次阻塞,重复(3)(4)(5)直到成功获取到锁。

  • lock

    // 公平锁 lock -> ReentrantLock.lock  -> FairSync.Lock  -> AQS 的 acquire
    //ReentrantLock.lock
    public void lock() {
        sync.lock();
    }
    //ReentrantLock.FairSync.lock
    final void lock() {
        acquire(1);
    }
    //调用 AQS 的 acquire
    public final void acquire(int arg) {
        //条件一:!tryAcquire 尝试获取锁 获取成功返回true  获取失败 返回false。
        //条件二:2.1:addWaiter 将当前线程封装成node入队 , 传入的节点模式为独占模式
        //       2.2:acquireQueued 挂起当前线程   唤醒后相关的逻辑..
        //      acquireQueued 返回true 表示挂起过程中线程被中断唤醒过..  false 表示未被中断过..
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            //再次设置中断标记位 true
            selfInterrupt();
    }
    // ReentrantLock.FairSync.tryAcquire
    protected final boolean tryAcquire(int acquires) {
        //当前线程
        final Thread current = Thread.currentThread();
        //AQS state 的值
        int c = getState();
        // state== 0 表示无锁状态
        if (c == 0) {
            //条件一:因为是公平锁,所以任何时候都需要检查一下,在当前线程之前队列中是否有等待者
            //hasQueuedPredecessors 返回 true 表示当前线程前面有等待者,当前线程需要入队等待
            // 返回 false,表示 当前线程前面没有等待者,直接尝试获取锁。。。
            //条件二:成功,抢锁成功,失败当前线程竞争失败
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                //设置为当前线程为 独占者 线程
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        /*
                    执行到这的情况:
                        c != 0 , > 0 的情况: 这种情况需要检查一下当前线程是不是独占模式,
                            因为 ReentranLock 是可以重入的
    
                    条件成立,则当前线程是独占锁线程
                 */
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            //边界判断,当重入的深度很深时,会导致 nextc < 0 ,Integer.MAX_VALUE,再加 1 就变负数了
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        /*
                    c == 0, cas 失败,未抢过别的线程
                    c > 0,且 exclusiveOwnerThread != currentThread (不是 独占模式)
                 */
        return false;
    }
    
  • unlock 的调用链

    ReentrantLcok.lock => Sync.release即AQS 的 release => ReentrantLock.Sync.tryRelease => AQS 的 unparkSuccessor

    image-20200726140826302

    释放锁的过程大致为:

    (1)将state的值减1;

    (2)如果state减到了0,说明已经完全释放锁了,唤醒下一个等待着的节点;

    //ReentrantLcok
    public void unlock() {
        sync.release(1);
    }
    //AQS 的 release
    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
    //ReentrantLock.Sync.tryRelease
    protected final boolean tryRelease(int releases) {
        int c = getState() - releases;
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        boolean free = false;
        if (c == 0) {
            free = true;
            setExclusiveOwnerThread(null);
        }
        setState(c);
        return free;
    }
    //AQS 的 unparkSuccessor
    private void unparkSuccessor(Node node) {
    
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);
        
        Node s = node.next;
        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)
            LockSupport.unpark(s.thread);
    }
    

2.4 独占锁 & 非公平锁[lock,unlock]

  • lock : 只有在 tryAcquire 部分实现方式不同

    ReentrantLock.lock => NonfairSync.lock => AQS.acquire => NonfairSync.tryAcquire => Sync.nonfairTryAcquire => AQS.addWaiter => AQS.acquireQueued

    image-20200726144101052

    (1)将state的值减1;

    (2)如果state减到了0,说明已经完全释放锁了,唤醒下一个等待着的节点;

  • unlock 和 公平锁的 unlock 相同

  • 独占锁的公平和非公平比较

    (1)一开始就尝试CAS更新状态变量state的值,如果成功了就获取到锁了;

    (2)在tryAcquire()的时候没有检查是否前面有排队的线程,直接上去获取锁才不管别人有没有排队呢;

    (3)总的来说,相对于公平锁,非公平锁在一开始就多了两次直接尝试获取锁的过程。

    (4)非公平锁的效率比公平锁效率高

    • 因为非公平模式会在一开始就尝试两次获取锁,如果当时正好state的值为0,它就会成功获取到锁,少了排队导致的阻塞/唤醒过程,并且减少了线程频繁的切换带来的性能损耗。

    • 非公平模式的弊端: 非公平模式有可能会导致一开始排队的线程一直获取不到锁,导致线程饿死。

    公平锁和非公平锁的区别

2.5 lockInterruptibly()

  • 支持线程中断

    它与 lock( ) 方法的主要区别在于

    • lockInterruptibly( ) 获取锁的时候如果线程中断了,会抛出一个异常,(下面代码的line9,32)
    • 而**lock()**不会管线程是否中断都会一直尝试获取锁,获取锁之后把自己标记为已中断,继续执行自己的逻辑,后面也会正常释放锁。 Thread.currentThread().interrupt();
    • ==线程中断,==只是在线程上打一个中断标志,并不会对运行中的线程有什么影响,具体需要根据这个中断标志干些什么,用户自己去决定。
  • 加锁逻辑

    //ReentrantLock 的  lockInterruptibly
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }
    //AQS 的 acquireInterruptibly
    public final void acquireInterruptibly(int arg)
        throws InterruptedException {
        //如果线程已经中断了,会抛出异常
        if (Thread.interrupted())
            throw new InterruptedException();
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }
    //ReentrantLock.Sync.tryAcquire
    //AQS 的 doAcquireInterruptibly
    private void doAcquireInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    //如果检测到中断标志,则抛出中断异常
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
    
  • lock 遇到异常? 是否处理

    package cn.yuan.test.juc;
    
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * @author yuan
     * @date 2020-07-26 16:23
     */
    public class ReentrantLockDeadLock {
    
    
        static int count = 0;
    
        static ReentrantLock lock = new ReentrantLock();
    
        public static void addCount(){
            lock.lock();
            try{
                if (Thread.currentThread().isInterrupted()){
                    System.out.println("检测到异常并处理---"+ count);
                    throw new InterruptedException();
                }
                count++;
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            CountDownLatch countDownLatch = new CountDownLatch(100);
    
            for (int i = 0; i < 100; i++) {
                Thread t = new Thread(()->{
                    for (int j = 0; j < 10; j++) {
                        addCount();
                    }
                    countDownLatch.countDown();
                });
                t.start();
                if (i == 50){
                    t.interrupt(); //加了 990,因为遇到异常抛出未进行 count++操作   不加 1000 正常
                }
            }
    
            countDownLatch.await();
            System.out.println(count);
            /*
                检测到异常并处理---500
                java.lang.InterruptedException
                    at cn.yuan.test.juc.ReentrantLockDeadLock.addCount(ReentrantLockDeadLock.java:24)
                    at cn.yuan.test.juc.ReentrantLockDeadLock.lambda$main$0(ReentrantLockDeadLock.java:40)
                    at java.lang.Thread.run(Thread.java:748)
                。。。//重复 10 次
                990
             */
        }
    
    }
    

2.6 tryLock

  • tryLock( ) : 尝试获取一次锁,成功了就返回true,没成功就返回false,不会继续尝试。

    // ReentrantLock.tryLock()
    public boolean tryLock() {
        return sync.nonfairTryAcquire(1);
    }
    // ReentrantLock.Sync.nonfairTryAcquire()
    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            if (compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
    
  • tryLock(long timeout, TimeUnit unit) : 尝试获取锁,并等待一段时间,如果在这段时间内都没有获取到锁,就返回false。

    //ReentrantLock.tryLock()
    public boolean tryLock(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }
    //AQS 的 tryAcquireNanos
     public final boolean tryAcquireNanos(int arg, long nanosTimeout)
         throws InterruptedException {
         if (Thread.interrupted())
             throw new InterruptedException();
         return tryAcquire(arg) ||
             doAcquireNanos(arg, nanosTimeout);
     }
    

2.7 条件锁(Condition)

  • 参考文献

    死磕 java同步系列之ReentrantLock源码解析(二)——条件锁

    深入浅出AQS之条件队列

    java1.8源码笔记AbstractQueuedSynchronizer详解

  • 使用方式示范

    public static void main(String[] args) throws InterruptedException {
        ReentrantLock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
    
        new Thread(()->{
            lock.lock();//1
            try{
                System.out.println("before await");//2
                condition.await();//3
                System.out.println("after await");//10
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }).start();
    
        //为了上面的线程先执行
        Thread.sleep(100);
        lock.lock();//4
        try{
            Thread.sleep(2000);
            System.out.println("before singal");//5
            condition.signal();//6
            System.out.println("after singal");//7
        }finally {
            lock.unlock();//8
            System.out.println("unlock singal");//9
        }
    }
    
  • 如何获取

    //ReentrantLock lock = new ReentrantLock();
    //Condition condition = lock.newCondition();
    
    //ReentrantLock.newCondition
    public Condition newCondition() {
        return sync.newCondition();
    }
    //Sync.newCondition
    final ConditionObject newCondition() {
        return new ConditionObject();
    }
    //AbstractQueuedSynchronizer.ConditionObject
    public class ConditionObject implements Condition, java.io.Serializable {
        /** First node of condition queue. */
        //指向条件队列的第一个node节点
        private transient Node firstWaiter;
        /** Last node of condition queue. */
        //指向条件队列的最后一个node节点
        private transient Node lastWaiter;
        //...
    }
    
  • await

    核心调用链:

    AQS.ConditionObject.await => AQS.ConditionObject.addConditionWaiter => AQS.fullyRelease => AQS.isOnSyncQueue

    => AQS.acquireQueued

    public final void await() throws InterruptedException {
      // 当前线程中断,则抛出异常
      if (Thread.interrupted())
          throw new InterruptedException();
     //将当前结点封装成 node 加入条件队列,并返回该结点
      Node node = addConditionWaiter();
     //将当前线程持有的锁全部释放(因为锁可能是重入的)
      int savedState = fullyRelease(node);
      int interruptMode = 0;
     //如果在同步队列中,则跳过 while,不在同步队列中,则将当前线程挂起 park
      while (!isOnSyncQueue(node)) {
          LockSupport.park(this);
          // 下面的代码等待 singal 唤醒后才会执行
          if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
              break;
      }
     //尝试获取锁,和之前的lock一样.直到获取锁成功才会返回,返回值为 当前线程的中断标志,
     //返回 true :表示在同步队列中被中断唤醒过.
      if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
          interruptMode = REINTERRUPT;
     //清空取消 cancelled 的结点
      if (node.nextWaiter != null) // clean up if cancelled
          unlinkCancelledWaiters();
     //中断处理相关
      if (interruptMode != 0)
          reportInterruptAfterWait(interruptMode);
    }
    
    • await 流程小结

    (1)新建一个节点加入到条件队列中去;

    (2)完全释放当前线程占有的锁;

    (3)阻塞当前线程,并等待条件的出现;

    (4)条件已出现(此时节点已经移到AQS的队列中),尝试获取锁;

    ​ 也就是说await()方法内部其实是 先释放锁->等待条件->再次获取锁的过程。

    • 对比

      条件队列 和 同步队列

      • 条件队列的头结点真正存储了值,是真实的结点
      • 同步队列的头结点只是 new Node( ) , 调用的是空参构造方法

      waitStatus 变化的情况:

      1. 首先,在条件队列中,新建节点的初始等待状态是CONDITION(-2);
      2. 其次,移到AQS的队列中时等待状态会更改为0(AQS队列节点的初始等待状态为0);
      3. 然后,在AQS的队列中如果需要阻塞,会把它上一个节点的等待状态设置为SIGNAL(-1);
      4. 最后,不管在Condition队列还是AQS队列中,已取消的节点的等待状态都会设置为CANCELLED(1);
      5. 另外,后面我们在共享锁的时候还会讲到另外一种等待状态叫PROPAGATE(-3)。
    image-20200728154002831
  • singal

    调用链:

    AQS.ConditionObject.signal => AQS.ConditionObject.doSignal => AQS.transferForSignal

    
    public final void signal() {
        //首先判断是不是当前线程持有的锁
        if (!isHeldExclusively())
            throw new IllegalMonitorStateException();
        Node first = firstWaiter;
        if (first != null)
            doSignal(first);
    }
    
    //从条件队列的头结点开始,寻找 CONDITION 状态的结点,迁移到 同步队列
    private void doSignal(Node first) {
        do {
            if ( (firstWaiter = first.nextWaiter) == null)
                lastWaiter = null;
            first.nextWaiter = null;
        } while (!transferForSignal(first) &&
                 (first = firstWaiter) != null);
    }
    //从条件队列中寻找一个 CONDITION 状态的结点,移动到 同步队列中
    final boolean transferForSignal(Node node) {
        
    	// If cannot change waitStatus, the node has been cancelled. (1)
        // 将 当前结点的 waitStatus 从 CONDITION 修改为 0,成功则表示从条件队列移动到 同步队列中
        //失败,则表示当前结点 是 取消状态.
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;
        //将条件队列的结点真正加入 同步队列中,并返回前一个结点
        Node p = enq(node);
        int ws = p.waitStatus;
        //如果前一个结点是取消状态 或者 将前一个结点更新为 SINGAL 状态失败 则 唤醒当前结点
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        // 如果更新上一个节点的等待状态为SIGNAL成功了
        // 则返回true,这时上面的循环不成立了,退出循环,也就是只通知了一个节点   
        // 此时当前节点还是阻塞状态
        // 也就是说调用signal()的时候并不会真正唤醒一个节点 
        // 只是把节点从条件队列移到AQS队列中
        return true;
    }
    

    signal()方法的大致流程为:

    (1)从条件队列的头节点开始寻找一个非取消状态的节点;

    (2)把它从条件队列移到AQS队列;

    (3)且只移动一个节点;

    注意,这里调用signal()方法后并不会真正唤醒一个节点,那么,唤醒一个节点是在啥时候呢?

    还记得开头例子吗?倒回去再好好看看,signal()方法后,最终会执行lock.unlock()方法,此时才会真正唤醒一个节点,唤醒的这个节点如果曾经是条件节点的话又会继续执行await()方法“分界线”下面的代码。

  • 流程图

    img

  • 总结

    (1)重入锁是指可重复获取的锁,即一个线程获取锁之后再尝试获取锁时会自动获取锁;

    (2)在ReentrantLock中重入锁是通过不断累加state变量的值实现的;

    (3)ReentrantLock的释放要跟获取匹配,即获取了几次也要释放几次;

    (4)ReentrantLock默认是非公平模式,因为非公平模式效率更高;

    (5)条件锁是指为了等待某个条件出现而使用的一种锁;

    (6)条件锁比较经典的使用场景就是队列为空时阻塞在条件notEmpty上;

    (7)ReentrantLock中的条件锁是通过AQS的ConditionObject内部类实现的;

    (8)await()和signal()方法都必须在获取锁之后释放锁之前使用

    (9)await()方法会新建一个节点放到条件队列中,接着完全释放锁,然后阻塞当前线程并等待条件的出现;

    (10)signal()方法会寻找条件队列中第一个可用节点移到AQS队列中;

    (11)在调用signal()方法的线程调用unlock()方法才真正唤醒阻塞在条件上的节点(此时节点已经在AQS队列中);

    (12)之后该节点会再次尝试获取锁,后面的逻辑与lock()的逻辑基本一致了。

2.8 waitStatus 状态的转变(未完待续)

  • 什么时候变成 CANCELLED 即 (1).

    在两个方法中将 waitStatus 状态改成了 1

    • fullyRelease 失败会 node.waitStatus = Node.CANCELLED;

    • cancelAcquire

      调用 cancelAcquire 的地方又包括:

      • acquireQueued 失败 调用 cancelAcquire()方法 => 这个方法会将当前的 node 出队
      • doAcquireShared doAcquireNanos doAcquireSharedInterruptibly doAcquireSharedNanos

    正常状况下是不会发生触发waitStatus值变为 1 的,只有在加锁/释放锁的过程过出现异常,导致当前获取/释放锁的过程不能继续时,才会发生.

  • 什么时候默认 waitStatus 为 0

    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;
    }
    
  • 什么时候变成 SINGAL 即 (-1)

  • 什么时候是 CONDITION (-2)

  • 什么时候是 PROPAGATE(-3)


3. 共享锁(CountDownLatch)

  • 参考文献

    死磕 java同步系列之CountDownLatch源码解析

  • 间介

    • CountDownLatch : 允许一个线程或者多个线程等待其他线程的操作执行完毕后在执行后续操作
    • CountDownLatch : 内部包含了 一个 Sync 内部类,继承 AQS ,没有 公平 / 非公平模式.
  • 构造函数

    //count 值 就是 AQS 中 state 的值
    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }
    
    Sync(int count) {
    	setState(count);
    }
    
  • await

    //COuntDownLatch.await
    public void await() throws InterruptedException {
    	sync.acquireSharedInterruptibly(1);
    }
    //AQS.acquireSharedInterruptibly
    public final void acquireSharedInterruptibly(int arg)
    	throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        // -1 < 0 ,即 state > 0, 1 > 0, state = 0
        //尝试获取锁, 获取锁失败入队
        if (tryAcquireShared(arg) < 0)
            // 只有state > 0 才会调用下面的方法
            //即将线程入同步队列等待(park, unpark)
            doAcquireSharedInterruptibly(arg);
    }
    //CountAownLatch.Sync.tryAcquireShared
    protected int tryAcquireShared(int acquires) {
    	return (getState() == 0) ? 1 : -1;
    }
    //AQS.doAcquireSharedInterruptibly
    private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        //入队,只要有一个结点入队 head -> newNode(tail),head != tail
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
    

    小结:

    (1)尝试获取锁,如果 state != 0,获取失败,进入同步队列,
    (2)将结点的前驱结点的 waitStatus 置为 -1,然后 park
    (3)等待调用 countDown 方法 将 state 减为 0,在依次唤醒队列中等待结点

  • countDown

    //CountDownLatch.countDown
    public void countDown() {
        sync.releaseShared(1);
    }
    //AQS.releaseShared
    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            //只有在最后一个线程执行完之后 state = 0,才会进入到这里
            doReleaseShared();
            return true;
        }
        return false;
    }
    //AQS.doReleaseShared
    //从 head 结点开始唤醒 同步队列 中的等待结点
    private void doReleaseShared() {
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            //自己多看看,为啥要有这个
            // 1) 唤醒之后, setHeadAndPropagate 还未执行完/已经执行完
            // 2) head == null
            // 3) head != null && head == tail
            if (h == head)                   // loop if head changed
                break;
        }
    }
    
  • 这个图有点问题… 就这样吧先
image-20200728094234573
  • 总结

    (1)CountDownLatch表示允许一个或多个线程等待其它线程的操作执行完毕后再执行后续的操作;

    (2)CountDownLatch使用AQS的共享锁机制实现;

    (3)CountDownLatch初始化的时候需要传入次数count;

    (4)每次调用countDown()方法count的次数减1;

    (5)每次调用await()方法的时候会尝试获取锁,这里的获取锁其实是检查AQS的state变量的值是否为0;

    (6)当count的值(也就是state的值)减为0的时候会唤醒排队着的线程(这些线程调用await()进入队列);

    思考:

    • https://stackoverflow.com/questions/53393351/can-countdownlatch-be-implemented-using-an-exclusive-lock
    • https://developer.aliyun.com/ask/130474

    image-20200728095007072

4. CyclicBarrier(ReentrantLock+Condition)

  • 参考文献

    死磕 java同步系列之CyclicBarrier源码解析——有图有真相

  • 间介

    CyclicBarrier,回环栅栏,它会阻塞一组线程直到这些线程同时达到某个条件才继续执行。它与CountDownLatch很类似,但又不同,CountDownLatch需要调用countDown()方法触发事件,而CyclicBarrier不需要,它就像一个栅栏一样,当一组线程都到达了栅栏处才继续往下走。

  • 构造方法

    //构造方法
    public CyclicBarrier(int parties, Runnable barrierAction) {
        //因为小于等于0 的barrier没有任何意义..
        if (parties <= 0) throw new IllegalArgumentException();
    
        this.parties = parties;
        //count的初始值 就是parties,后面当前代每到位一个线程,count--
        this.count = parties;
        this.barrierCommand = barrierAction;
    }
    public CyclicBarrier(int parties) {
        this(parties, null);
    }
    //内部类
    private static class Generation {
        //表示当前“代”是否被打破,如果代被打破 ,那么再来到这一代的线程 就会直接抛出 BrokenException异常
        //且在这一代 挂起的线程 都会被唤醒,然后抛出 BrokerException异常。
        boolean broken = false;
    }
    
    //因为barrier实现是依赖于Condition条件队列的,condition条件队列必须依赖lock才能使用。
    private final ReentrantLock lock = new ReentrantLock();
    //线程挂起实现使用的 condition 队列。条件:当前代所有线程到位,这个条件队列内的线程 才会被唤醒。
    private final Condition trip = lock.newCondition();
    
    //Barrier需要参与进来的线程数量
    private final int parties;
    //当前代 最后一个到位的线程 需要执行的事件
    private final Runnable barrierCommand;
    //表示barrier对象 当前 “代”
    private Generation generation = new Generation();
    //表示当前“代”还有多少个线程 未到位。
    private int count;
    
  • await, 没有CountDownLatch 中的 countDown 方法

    //CyclicBarrier.await
    public int await() throws InterruptedException, BrokenBarrierException {
     try {
         return dowait(false, 0L);
     } catch (TimeoutException toe) {
         throw new Error(toe); // cannot happen
     }
    }
    //CyclicBarrier.dowait
    private int dowait(boolean timed, long nanos)
     throws InterruptedException, BrokenBarrierException,
    TimeoutException {
     final ReentrantLock lock = this.lock;
     lock.lock();
     try {
         final Generation g = generation;
    
         if (g.broken)
             throw new BrokenBarrierException();
    
         if (Thread.interrupted()) {
             breakBarrier();
             throw new InterruptedException();
         }
    
         int index = --count;
         //最后一个线程到达这里(此时 count 值减为 0)
         if (index == 0) {  // tripped
             boolean ranAction = false;
             try {
                 final Runnable command = barrierCommand;
                 if (command != null)
                     command.run();
                 ranAction = true;
                 //调用下一代的方法,
                 //1.唤醒所有park 的线程 2.重置了 count 的值 3. 重置了 generation 
                 nextGeneration();
                 return 0;
             } finally {
                 if (!ranAction)
                     breakBarrier();
             }
         }
    
         // loop until tripped, broken, interrupted, or timed out
         //不是最后一个线程执行的逻辑
         for (;;) {
             try {
                 if (!timed)
                     //调用 Condition 的 await 方法
                     //将当前结点入同步队列,然后释放锁, park 等待唤醒
                     //未发生异常情况到这就结束了,执行到这里挂起,等待别人唤醒 signal.(如果是中断唤醒还要进行下面的处理)
                     trip.await();
                 else if (nanos > 0L)
                     //超时等待的方法
                     nanos = trip.awaitNanos(nanos);
             } catch (InterruptedException ie) {
                 //异常 IE 来自 在条件队列中时,收到中断信号
                 if (g == generation && ! g.broken) {
                     breakBarrier();
                     throw ie;
                 } else {
                     // We're about to finish waiting even if we had not
                     // been interrupted, so this interrupt is deemed to
                     // "belong" to subsequent execution.
                     Thread.currentThread().interrupt();
                 }
             }
    
             if (g.broken)
                 throw new BrokenBarrierException();
    		 //正常情况下,这都是不等的,因为在上面 await中被park了,
             //被唤醒之后表示 signall,此时开启了下一代 nextGengeration()
             if (g != generation)
                 return index;
    
             if (timed && nanos <= 0L) {
                 breakBarrier();
                 throw new TimeoutException();
             }
         }
     } finally {
         lock.unlock();
     }
    }
    
    private void nextGeneration() {
        //将在trip条件队列内挂起的线程 全部唤醒
        // signal completion of last generation
        trip.signalAll();
    
        //重置count为parties
        // set up next generation
        count = parties;
    
        //开启新的一代..使用一个新的 generation对象,表示新的一代,新的一代和上一代 没有任何关系。
        generation = new Generation();
    }
    

    小结:

    (1)最后一个线程走上面的逻辑,当count减为0的时候,打破栅栏,它调用nextGeneration()方法通知条件队列中的等待线程转移到AQS的队列中等待被唤醒,并进入下一代。

    (2)非最后一个线程走下面的for循环逻辑,这些线程会阻塞在condition的await()方法处,它们会加入到条件队列中,等待被通知当它们唤醒的时候已经更新换“代”了,这时候返回。

  • 图解(画的很好)

    img

  • 总结

    (1)CyclicBarrier会使一组线程阻塞在await()处,当最后一个线程到达时唤醒(只是从条件队列转移到AQS队列中)前面的线程大家再继续往下走;

    (2)CyclicBarrier不是直接使用AQS实现的一个同步器;

    (3)CyclicBarrier基于ReentrantLock及其Condition实现整个同步逻辑;

  • 和 CountDownLatch 的比较

    (1)两者都能实现阻塞一组线程等待被唤醒;

    (2)前者是最后一个线程到达时自动唤醒

    (3)后者是通过==显式地调用countDown()==实现的;

    (4)前者是通过重入锁及其条件锁实现的,后者是直接基于==AQS(共享模式)==实现的;

    (5)前者具有“代”的概念,可以重复使用,后者只能使用一次;

    (6)前者只能实现多个线程到达栅栏处一起运行;

    (7)后者不仅可以实现多个线程等待一个线程条件成立,还能实现一个线程等待多个线程条件成立(详见CountDownLatch那章使用案例);

    • 实现方式上的不同 ReentrantLock Condition , AQS
    • 使用上不同: 1) 重复使用? 2)需要调用 countDown()
    • ???实现功能???: 多对一 / 多对一 &&一对多
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch start = new CountDownLatch(1);
        CountDownLatch done = new CountDownLatch(5);
    
        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                try {
                    System.out.println("done thread is waiting");
                    start.await();
                    System.out.println("done doing something");
                    done.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
            }).start();
        }
    
        TimeUnit.SECONDS.sleep(2);
        System.out.println("main do something");
        start.countDown();
        System.out.println("============");
        done.await();
        System.out.println("***************");
        /*
        done thread is waiting
        done thread is waiting
        done thread is waiting
        done thread is waiting
        done thread is waiting
        main do something
        ============
        done doing something
        done doing something
        done doing something
        done doing something
        done doing something
        ***************
        */
    }
    

5. Semaphore(公平/非公平+共享)

  • 参考文献

    死磕 java同步系列之Semaphore源码解析

  • 构造方法

    //构造函数
    public Semaphore(int permits) {
        sync = new NonfairSync(permits);
    }
    
    public Semaphore(int permits, boolean fair) {
        sync = fair ? new FairSync(permits) : new NonfairSync(permits);
    }
    //内部类
    abstract static class Sync extends AbstractQueuedSynchronizer
    static final class NonfairSync extends Sync 
    static final class FairSync extends Sync
    
  • 简介

    Semaphore,信号量,它保存了一系列的许可(permits),每次调用acquire()都将消耗一个许可,每次调用release()都将归还一个许可。

    Semaphore通常用于限制同一时间对共享资源的访问次数上,也就是常说的限流。

  • acquire (公平模式)

    //Semaphore.acquire
    public void acquire() throws InterruptedException {
     	sync.acquireSharedInterruptibly(1);
    }
    
    //AQS.acquireSharedInterruptibly
    public final void acquireSharedInterruptibly(int arg)
     	throws InterruptedException {
         if (Thread.interrupted())
             throw new InterruptedException();
        // 小于 0 ,表示获取失败.
         if (tryAcquireShared(arg) < 0)
             doAcquireSharedInterruptibly(arg);
    }
    //Semaphore.FairSync.tryAcquireShared
    //首先判断同步队列中是否有等待者,有则返回 -1,需要排队等待, 没有则直接获取许可,返回剩余的许可数
    protected int tryAcquireShared(int acquires) {
         for (;;) {
             if (hasQueuedPredecessors())
                 return -1;
             int available = getState();
             int remaining = available - acquires;
             if (remaining < 0 ||
                 compareAndSetState(available, remaining))
                 return remaining;
         }
    }
    //AQS.doAcquireSharedInterruptibly
    // 将当前 线程 封装成 node 入同步队列,如果 node 的前驱结点为 head,再次尝试获取许可,
    //如果不是则将前驱的等待状态置为 -1, park, 等待唤醒. 唤醒之后再次尝试获取
    //获取许可成功: 会将同步队列的 head 出队,传递唤醒后面的结点
    private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
    
  • release

    //Semaphore.release
    public void release() {
        sync.releaseShared(1);
    }
    //AQS.releaseShared
    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            //成功归还 许可,此时有新的许可,需要 unpark 同步队列中的结点
            doReleaseShared();
            return true;
        }
        return false;
    }
    //Semphore.Sync.tryReleaseShared
    //归还许可, 成功归还许可,返回true,失败自旋再次归还
    protected final boolean tryReleaseShared(int releases) {
        for (;;) {
            int current = getState();
            int next = current + releases;
            if (next < current) // overflow
                throw new Error("Maximum permit count exceeded");
            if (compareAndSetState(current, next))
                return true;
        }
    }
    //AQS.doReleaseShared
    // head = -1 时, 将 waitStatus 改为 0,然后唤醒 head.next
    private void doReleaseShared() {
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }
    
  • 总结

    (1)Semaphore,也叫信号量,通常用于控制同一时刻对共享资源的访问上,也就是限流场景;

    (2)Semaphore的内部实现是基于AQS的共享锁来实现的;

    (3)Semaphore初始化的时候需要指定许可的次数,许可的次数是存储在state中;

    (4)获取一个许可时,则state值减1; //acquire()

    (5)释放一个许可时,则state值加1;//release()

    (6)可以动态减少n个许可; //acquire(int permits)

    (7)可以动态增加n个许可吗?//release(int permits)

  • 如何使用 Semaphore 实现限流?

    限流,即在流量突然增大的时候,上层要能够限制住突然的大流量对下游服务的冲击,在分布式系统中限流一般做在网关层,当然在个别功能中也可以自己简单地来限流,比如秒杀场景,假如只有10个商品需要秒杀,那么,服务本身可以限制同时只进来100个请求,其它请求全部作废,这样服务的压力也不会太大。

    /**
     * @author yuan
     * @date 2020-07-28 15:54
     */
    public class SemaphoreTest {
    
        public static final Semaphore SEMAPHORE = new Semaphore(10);
        public static final AtomicInteger failCount = new AtomicInteger(0);
        public static final AtomicInteger successCount = new AtomicInteger(0);
    
        public static void main(String[] args) {
    
            for (int i = 0; i < 100; i++) {
                new Thread(()->{
                    if(secKill()){
                        System.out.println(Thread.currentThread().getName()+"-成功抢到秒杀许可");
                    }
                }).start();
            }
        }
        /**
         * 只能有 100 个人强到许可,进入秒杀
         * @return true,成功抢到许可
         */
        public static boolean secKill(){
            if (!SEMAPHORE.tryAcquire()){
                //许可已经发放完毕,秒杀失败
                System.out.println("no permits,count = " + failCount.incrementAndGet());
                return false;
            }
            try {
                //处理业务逻辑
                Thread.sleep(1000);
                System.out.println("secKill success, count = "+successCount.incrementAndGet());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                SEMAPHORE.release();
            }
            return true;
        }
    }
    

6. ReentrantReadWriteLock

  • 参考文献

    死磕 java同步系列之ReentrantReadWriteLock源码解析

    ReentrantReadWriteLock详解

    老外的一篇文章

    这里有一个读锁/写锁的获取流程图

    读写锁——ReentrantReadWriteLock原理详解

    待解决的问题: acquire line 12 看不到,埋坑 等之后再看吧,说不定就懂了

  • 简介

    读写锁是一种特殊的锁,它把对共享资源的访问分为读访问和写访问,多个线程可以同时对共享资源进行读访问,但是同一时间只能有一个线程对共享资源进行写访问,使用读写锁可以极大地提高并发量。


    读读 共享

    读写互斥

    写读互斥

    写写互拆

    /**
     * @author yuan
     * @date 2020-07-28 21:30
     */
    public class ReentrantReadWriteLockTest {
    
        private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    
        public void read() {
            try {
                lock.readLock().lock();
                System.out.println(Thread.currentThread().getName() + " start");
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName() + " end");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.readLock().unlock();
            }
        }
    
        public void write(){
            lock.writeLock().lock();
            try{
                System.out.println(Thread.currentThread().getName() + " start");
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName() + " end");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                lock.writeLock().unlock();
            }
        }
        
        public static void main(String[] args) throws InterruptedException {
            ReentrantReadWriteLockTest test = new ReentrantReadWriteLockTest();
            //读读
            /*for (int i = 0; i < 2; i++) {
                new Thread(()->{
                    test.read();
                }).start();
            }*/
            /*
                Thread-0 start
                Thread-1 start
                Thread-0 end
                Thread-1 end
             */
            //写写
            /*for (int i = 0; i < 2; i++) {
                new Thread(()->{
                    test.write();
                }).start();
            }*/
            /*
                互斥,同时只能有一个线程持有锁
                Thread-0 start
                Thread-0 end
                Thread-1 start
                Thread-1 end
            * */
    
            //写 读
            /*new Thread(()->{
                test.write();
            }).start();
            //确保上面的先执行
            Thread.sleep(100);
            System.out.println("===");
    
            new Thread(()->{
                test.read();
            }).start();*/
            /*
                Thread-0 start
                ===
                Thread-0 end
                Thread-1 start
                Thread-1 end
             */
    
            //读写
            new Thread(()->{
                test.read();
            }).start();
            //确保上面的先执行
            Thread.sleep(100);
            System.out.println("===");
    
            new Thread(()->{
                test.write();
            }).start();
            /*
                Thread-0 start
                ===
                Thread-0 end
                Thread-1 start
                Thread-1 end
             */
        }
    }
    
  • 构造方法

    • 继承关系

      image-20200728214651157

    // 实现接口
    public class ReentrantReadWriteLock implements ReadWriteLock
    //内部类
    abstract static class Sync extends AbstractQueuedSynchronizer
    static final class NonfairSync extends Sync
    static final class FairSync extends Sync 
        
    public static class ReadLock implements Lock
    public static class WriteLock implements Lock
    //构造方法
    public ReentrantReadWriteLock() {
        this(false);
    }
    public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
    }
    
    private final ReentrantReadWriteLock.ReadLock readerLock;
    /** Inner class providing writelock */
    private final ReentrantReadWriteLock.WriteLock writerLock;
    /** Performs all synchronization mechanics */
    final Sync sync;
    //=======================================================
    //参考: https://www.jianshu.com/p/6923c126e762
    //AQS 中的属性
    //高 16 位表示是共享锁(读锁)被获取的次数-sharedCount, 低 16 位表示独占锁(写锁)被获取的次数-exclusiveCount
    private volatile int state;
    
    // tid 线程的 id, count:记录着线程获取锁的次数,重入锁
    static final class HoldCounter {
        int count = 0;
        // Use id, not reference, to avoid garbage retention
        final long tid = getThreadId(Thread.currentThread());
    }
    //用于保存 获取读锁的线程 以及对应线程获取的读锁的数量,用来计算重入的次数
    //写锁重入的次数就是 state 低 16 位,因为写锁是独占锁,不会被多个线程同时持有
    static final class ThreadLocalHoldCounter
        extends ThreadLocal<HoldCounter> {
        public HoldCounter initialValue() {
            return new HoldCounter();
        }
    }
    //有了 ThreadLocalHoldCounter 为什么还要 缓存,记录了最后一次 readLock 的线程
    //原因是这样的, 但多数情况在进行线程 acquire readLock后不久就会进行相应的release,
    //而从  cachedHoldCounter 获取, 省去了从 ThreadLocal 中 lookup 的操作
    //(其实就是节省资源, ThreadLocal 中的查找需要遍历数组???
    private transient HoldCounter cachedHoldCounter;
    
  • 写锁的获取与释放 (非公平) 区别在于 writerShouldBlock 的实现不同

    • acquire 流程图(有点问题好像)

    image-20200729102220945

    • 步骤:

      (1) 尝试获取锁,成功则直接返回,失败则放入同步队列挂起,等待唤醒

      (2)tryAcquire:

      • 如果有线程持有 读锁,则当前线程写锁获取失败
      • 如果其他线程持有写锁,当前线程获取写锁失败
      • 如果当前线程持有写锁,为 锁的重入则返回 true
      • 如果当前线程未持有锁, (writerShouldBlock), 非公平锁 则通过 CAS 抢锁, 成功 返回 true, 失败返回 false
      • 如果是 公平锁,则先判断同步队列中是否有等待 node
      protected final boolean tryAcquire(int acquires) {
          /*
                   * Walkthrough:
                   * 1. If read count nonzero or write count nonzero
                   *    and owner is a different thread, fail.
                   * 2. If count would saturate, fail. (This can only
                   *    happen if count is already nonzero.)
                   * 3. Otherwise, this thread is eligible for lock if
                   *    it is either a reentrant acquire or
                   *    queue policy allows it. If so, update state
                   *    and set owner.
                   */
          Thread current = Thread.currentThread();
          int c = getState();
          int w = exclusiveCount(c);
          if (c != 0) {
              // (Note: if c != 0 and w == 0 then shared count != 0)
              if (w == 0 || current != getExclusiveOwnerThread())
                  return false;
              if (w + exclusiveCount(acquires) > MAX_COUNT)
                  throw new Error("Maximum lock count exceeded");
              // Reentrant acquire
              setState(c + acquires);
              return true;
          }
          if (writerShouldBlock() ||
              !compareAndSetState(c, c + acquires))
              return false;
          setExclusiveOwnerThread(current);
          return true;
      }
      
    • release 流程图

      image-20200729103556274

    • release 步骤

      (1) 尝试释放锁,释放成功则 唤醒 head.next,即同步队列中等待的结点

      (2) tryRelease :分为两步,先将持锁数量减一, 然后在判断持有锁的数量 == 0, 为 0 返回 true

    • writerShouldBlock

      //fair
      final boolean writerShouldBlock() {
          return hasQueuedPredecessors();
      }
      //nofair
       final boolean writerShouldBlock() {
           return false; // writers can always barge
       }
      

  • 读锁的获取与释放 (非公平) readerShouldBlock 的实现不同

    • acquire line 12 看不到,埋坑

      //读锁的 尝试获取锁,成功 1,失败 -1
      protected final int tryAcquireShared(int unused) {
      	//
          Thread current = Thread.currentThread();
          int c = getState();
          //别的线程持有写锁,直接返回 -1,获取锁失败
          if (exclusiveCount(c) != 0 &&
              getExclusiveOwnerThread() != current)
              return -1;
          // 可能情况: 当前线程是写锁 / 读锁,   写锁 => 会设计到锁的降级,看下面的案例
          int r = sharedCount(c);
          //这边看不太懂了,不知道什么时候会获取读锁失败? 感觉正常情况下都不会失败
          if (!readerShouldBlock() &&
              r < MAX_COUNT &&
              compareAndSetState(c, c + SHARED_UNIT)) {
              if (r == 0) {
                  firstReader = current;
                  firstReaderHoldCount = 1;
              } else if (firstReader == current) {
                  firstReaderHoldCount++;
              } else {
                  HoldCounter rh = cachedHoldCounter;
                  if (rh == null || rh.tid != getThreadId(current))
                      cachedHoldCounter = rh = readHolds.get();
                  else if (rh.count == 0)
                      readHolds.set(rh);
                  rh.count++;
              }
              return 1;
          }
          //如果当前线程是写锁,则再次尝试获取锁. 这里会涉及到
          return fullTryAcquireShared(current);
      }
      
    • readerShouldBlock

      //fair
       final boolean readerShouldBlock() {
           return hasQueuedPredecessors();
       }
      //nofair
      final boolean readerShouldBlock() {
          /* As a heuristic to avoid indefinite writer starvation,
                   * block if the thread that momentarily appears to be head
                   * of queue, if one exists, is a waiting writer.  This is
                   * only a probabilistic effect since a new reader will not
                   * block if there is a waiting writer behind other enabled
                   * readers that have not yet drained from the queue.
                   */
          // 同步队列不为空时, head 为 Exclusive 则返回 true,否则 返回 false 
          return apparentlyFirstQueuedIsExclusive();
      }
      
  • 小结

    当前线程要想获得写锁,则 state = 0,无锁状态 或者 重入锁

    当前线程要想获得读锁,则 考虑下面得情况:

    state != 0 ,

    • 其他线程持有写锁 ,当前线程不能获取读锁
    • 其他线程持有读锁,当前线程可以获取读锁
    • 当前线程持有写锁,当前线程可以在获取读锁,且获取读锁可以重入 这里好像不太对,暂时理不清, 结合 readerShouldBlock ReentrantReadWriteLock详解
    • 当前线程持有读锁,当前线程可以获取读锁.

    state == 0 .则当前线程争抢读锁.

    读锁/写锁获取流程(大概)

    image-20200729141409147


  • 锁的升级和降低

    • 锁的降级: 写锁 => 读锁 对于同一个线程,先加读锁,未释放读锁,在申请写锁,这属于锁的升级,因为 ReentrantReadWriteLock 不支持锁的升级,所以此时会发生死锁

    • 锁的升级: 读锁 => 写锁. 对于同一个线程,先加写锁,写锁未释放,在加读锁,读锁可以成功获取,此时释放写锁,会将写锁降级为读锁.

    /**
     * @author yuan
     * @date 2020-07-29 10:02
     */
    public class LockUpAndDown {
    
        static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    
        //先读后写,不能锁升级.(同一个线程)
        public static void lockUp(){
            lock.readLock().lock();
            System.out.println("read lock");
            lock.writeLock().lock();  // 只有等 读锁释放了,写锁才能获取
            System.out.println("write lock");
            lock.readLock().unlock();
            lock.writeLock().unlock();
            /*
                read lock
                然后一直阻塞
             */
        }
    	//(同一个线程)
        public static  void lockDown(){
            lock.writeLock().lock();
            System.out.println("write lock");
            lock.readLock().lock();
            System.out.println("read lock");
            lock.writeLock().unlock();
            lock.readLock().unlock();
            System.out.println("end");
            /*
                write lock
                read lock
                end
             */
        }
    
        public static void main(String[] args) {
            lockDown();
        }
    }
    
    //Oracle 官方例子
    public  class CachedData {
        Object data;
        volatile boolean cacheValid;
        final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    
        void processCachedData() {
            rwl.readLock().lock();
            if (!cacheValid) {
                // Must release read lock before acquiring write lock
                rwl.readLock().unlock();
                rwl.writeLock().lock(); //写锁
                try {
                    // Recheck state because another thread might have
                    // acquired write lock and changed state before we did.
                    if (!cacheValid) {
                        System.out.println("write before:"+data);
                        data = new Object();//随便给 data 赋个值
                        cacheValid = true;
                    }
                    // Downgrade by acquiring read lock before releasing write lock
                    rwl.readLock().lock();
                } finally {
                    rwl.writeLock().unlock(); // Unlock write, still hold read
                }
            }
    
            try {
    //            use(data);
                System.out.println("write after:"+data);
            } finally {
                rwl.readLock().unlock();
            }
        }
    
        public static void main(String[] args) {
            CachedData cd = new CachedData();
            cd.processCachedData();
        }
        /*
        	在释放写锁前需要先申请读锁,也既锁降级。
        	具体原因是,如果不先获取读锁就释放写锁,那么在执行后面的use(data)时,data有可能被其它线程修改。
        	加了读锁之后,其他写线程无法获取写锁,则不会被修改
        */
    }
    

7. end

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值