• 面试题: ReentrantLock源码探究


    ReentrantLock源码探究

    后面的流程可以结合:https://www.cnblogs.com/dalianpai/p/14202617.html 一起看

    相对于 synchronized 它具备如下特点

    • 可中断
    • 可以设置超时时间
    • 可以设置为公平锁
    • 支持多个条件变量
    • 与 synchronized 一样,都支持可重入

    可重入

    可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住

    /**
     * @author WGR
     * @create 2020/12/28 -- 23:05
     */
    public class Test3 {
        static ReentrantLock lock = new ReentrantLock();
        public static void main(String[] args) {
            method1();
        }
        public static void method1() {
            lock.lock();
            try {
                System.out.println("execute method1");
                method2();
            } finally {
                lock.unlock();
            }
        }
        public static void method2() {
            lock.lock();
            try {
                System.out.println("execute method2");
                method3();
            } finally {
                lock.unlock();
            }
        }
        public static void method3() {
            lock.lock();
            try {
                System.out.println("execute method3");
            } finally {
                lock.unlock();
            }
        }
    }
    
    

    image-20201228230749857

    可打断

    /**
     * @author WGR
     * @create 2020/12/28 -- 23:10
     */
    public class Test4 {
    
        public static void main(String[] args) throws InterruptedException {
            ReentrantLock lock = new ReentrantLock();
            Thread t1 = new Thread(() -> {
                System.out.println("启动...");
                try {
                    lock.lockInterruptibly();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    System.out.println("等锁的过程中被打断");
                    return;
                }
                try {
                    System.out.println("获得了锁");
                } finally {
                    lock.unlock();
                }
            }, "t1");
            System.out.println("获得了锁");
            lock.lock();
            t1.start();
            try {
                Thread.sleep(1000);
                t1.interrupt();
                System.out.println("执行打断");
            } finally {
                lock.unlock();
            }
        }
    
    }
    
    

    image-20201228231639163

    注意如果是不可中断模式,那么即使使用了 interrupt 也不会让等待中断。

    /**
     * @author WGR
     * @create 2020/12/29 -- 0:09
     */
    public class Test5 {
        public static void main(String[] args) {
            ReentrantLock lock = new ReentrantLock();
            Thread t1 = new Thread(() -> {
                System.out.println("启动...");
                lock.lock();
                try {
                    System.out.println("获得了锁");
                } finally {
                    lock.unlock();
                }
            }, "t1");
            lock.lock();
            System.out.println("获得了锁");
            t1.start();
            try {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                t1.interrupt();
                System.out.println("执行打断");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } finally {
                System.out.println("释放了锁");
                lock.unlock();
            }
        }
    }
    
    

    image-20201229001318250

    公平锁

    ReentrantLock 默认是不公平的

    /**
     * @author WGR
     * @create 2020/12/29 -- 0:09
     */
    public class Test5 {
        public static void main(String[] args) throws InterruptedException {
            ReentrantLock lock = new ReentrantLock(false);
            lock.lock();
            for (int i = 0; i < 500; i++) {
                new Thread(() -> {
                    lock.lock();
                    try {
                        System.out.println(Thread.currentThread().getName() + " running...");
                    } finally {
                        lock.unlock();
                    }
                }, "t" + i).start();
            }
    // 1s 之后去争抢锁
            Thread.sleep(1000);
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + " start...");
                lock.lock();
                try {
                    System.out.println(Thread.currentThread().getName() + " running...");
                } finally {
                    lock.unlock();
                }
            }, "强行插入").start();
            lock.unlock();
        }
    }
    
    

    image-20201229001822968

    改成公平的会出现在最后:

    image-20201229001648889

    类的继承图

    image-20201228231725963

    非公平锁实现原理

    没有竞争时

    image-20201228231833735

    第一个竞争出现时

    image-20201228232019982

    Thread-1 执行了

    1. CAS 尝试将 state 由 0 改为 1,结果失败
    2. 进入 tryAcquire 逻辑,这时 state 已经是1,结果仍然失败
    3. 接下来进入 addWaiter 逻辑,构造 Node 队列
      图中黄色三角表示该 Node 的 waitStatus 状态,其中 0 为默认正常状态,Node 的创建是懒惰的,其中第一个 Node 称为 Dummy(哑元)或哨兵,用来占位,并不关联线程

    image-20201228232211717

    当前线程进入 acquireQueued 逻辑

    1. acquireQueued 会在一个死循环中不断尝试获得锁,失败后进入 park 阻塞
    2. 如果自己是紧邻着 head(排第二位),那么再次 tryAcquire 尝试获取锁,当然这时 state 仍为 1,失败
    3. 进入 shouldParkAfterFailedAcquire 逻辑,将前驱 node,即 head 的 waitStatus 改为 -1,这次返回 false

    image-20201228232250908

    1. shouldParkAfterFailedAcquire 执行完毕回到 acquireQueued ,再次 tryAcquire 尝试获取锁,当然这时state 仍为 1,失败
    2. 当再次进入 shouldParkAfterFailedAcquire 时,这时因为其前驱 node 的 waitStatus 已经是 -1,这次返回true
    3. 进入 parkAndCheckInterrupt, Thread-1 park(灰色表示)

    image-20201228232417113

    再次有多个线程经历上述过程竞争失败,变成这个样子

    image-20201228232449225

    Thread-0 释放锁,进入 tryRelease 流程,如果成功

    • 设置 exclusiveOwnerThread 为 null
    • state = 0

    image-20201228232538721

    当前队列不为 null,并且 head 的 waitStatus = -1,进入 unparkSuccessor 流程,找到队列中离 head 最近的一个 Node(没取消的),unpark 恢复其运行,本例中即为 Thread-1回到 Thread-1 的 acquireQueued 流程

    image-20201228232604950

    如果加锁成功(没有竞争),会设置exclusiveOwnerThread 为 Thread-1,state = 1head 指向刚刚 Thread-1 所在的 Node,该 Node 清空 Thread,原本的 head 因为从链表断开,而可被垃圾回收
    如果这时候有其它线程来竞争(非公平的体现),例如这时有 Thread-4 来了

    image-20201228232638826

    如果不巧又被 Thread-4 占了先Thread-4 被设置为 exclusiveOwnerThread,state = 1,Thread-1 再次进入 acquireQueued 流程,获取锁失败,重新进入 park 阻塞。

    ReentrantLock源码

    加锁源码

    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;
    
        // 加锁实现
        final void lock() {
    // 首先用 cas 尝试(仅尝试一次)将 state 从 0 改为 1, 如果成功表示获得了独占锁
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
    // 如果尝试失败,进入 ㈠
                acquire(1);
        }
    
        // ㈠ AQS 继承过来的方法, 方便阅读, 放在此处
        public final void acquire(int arg) {
    // ㈡ tryAcquire
            if (
                    !tryAcquire(arg) &&
    // 当 tryAcquire 返回为 false 时, 先调用 addWaiter ㈣, 接着 acquireQueued ㈤
                            acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
            ) {
                selfInterrupt();
            }
        }
    
        // ㈡ 进入 ㈢
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    
        // ㈢ Sync 继承过来的方法, 方便阅读, 放在此处
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
    // 如果还没有获得锁
            if (c == 0) {
    // 尝试用 cas 获得, 这里体现了非公平性: 不去检查 AQS 队列
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
    // 如果已经获得了锁, 线程还是当前线程, 表示发生了锁重入
            else if (current == getExclusiveOwnerThread()) {
    // state++
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
    // 获取失败, 回到调用处
            return false;
        }
    
        // ㈣ AQS 继承过来的方法, 方便阅读, 放在此处
        private Node addWaiter(Node mode) {
    // 将当前线程关联到一个 Node 对象上, 模式为独占模式
            Node node = new Node(Thread.currentThread(), mode);
    // 如果 tail 不为 null, cas 尝试将 Node 对象加入 AQS 队列尾部
            Node pred = tail;
            if (pred != null) {
                node.prev = pred;
                if (compareAndSetTail(pred, node)) {
    // 双向链表
                    pred.next = node;
                    return node;
                }
            }
    // 尝试将 Node 加入 AQS, 进入 ㈥
            enq(node);
            return node;
        }
    
        // ㈥ AQS 继承过来的方法, 方便阅读, 放在此处
        private Node enq(final Node node) {
            for (; ; ) {
                Node t = tail;
                if (t == null) {
    // 还没有, 设置 head 为哨兵节点(不对应线程,状态为 0)
                    if (compareAndSetHead(new Node())) {
                        tail = head;
                    }
                } else {
    // cas 尝试将 Node 对象加入 AQS 队列尾部
                    node.prev = t;
                    if (compareAndSetTail(t, node)) {
                        t.next = node;
                        return t;
                    }
                }
            }
        }
    
        // ㈤ AQS 继承过来的方法, 方便阅读, 放在此处
        final boolean acquireQueued(final Node node, int arg) {
            boolean failed = true;
            try {
                boolean interrupted = false;
                for (; ; ) {
                    final Node p = node.predecessor();
    // 上一个节点是 head, 表示轮到自己(当前线程对应的 node)了, 尝试获取
                    if (p == head && tryAcquire(arg)) {
    // 获取成功, 设置自己(当前线程对应的 node)为 head
                        setHead(node);
                    // 上一个节点 help GC
                        p.next = null;
                        failed = false;
    // 返回中断标记 false
                        return interrupted;
                    }
                    if (
    // 判断是否应当 park, 进入 ㈦
                            shouldParkAfterFailedAcquire(p, node) &&
    // park 等待, 此时 Node 的状态被置为 Node.SIGNAL ㈧
                                    parkAndCheckInterrupt()
                    ) {
                        interrupted = true;
                    }
                }
            } finally {
                if (failed)
                    cancelAcquire(node);
            }
        }
    
        // ㈦ AQS 继承过来的方法, 方便阅读, 放在此处
        private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    // 获取上一个节点的状态
            int ws = pred.waitStatus;
            if (ws == Node.SIGNAL) {
    // 上一个节点都在阻塞, 那么自己也阻塞好了
                return true;
            }
    // > 0 表示取消状态
            if (ws > 0) {
    // 上一个节点取消, 那么重构删除前面所有取消的节点, 返回到外层循环重试
                do {
                    node.prev = pred = pred.prev;
                } while (pred.waitStatus > 0);
                pred.next = node;
            } else {
    // 这次还没有阻塞
    // 但下次如果重试不成功, 则需要阻塞,这时需要设置上一个节点状态为 Node.SIGNAL
                compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
            }
            return false;
        }
    
        // ㈧ 阻塞当前线程
        private final boolean parkAndCheckInterrupt() {
            LockSupport.park(this);
            return Thread.interrupted();
        }
    }
    
    

    注意:是否需要 unpark 是由当前节点的前驱节点的 waitStatus == Node.SIGNAL 来决定,而不是本节点的waitStatus 决定。

    解锁源码

    static final class NonfairSync extends Sync {
        // 解锁实现
        public void unlock() {
            sync.release(1);
        }
        // AQS 继承过来的方法, 方便阅读, 放在此处
        public final boolean release(int arg) {
    // 尝试释放锁, 进入 ㈠
            if (tryRelease(arg)) {
    // 队列头节点 unpark
                Node h = head;
                if (
    // 队列不为 null
                        h != null &&
    // waitStatus == Node.SIGNAL 才需要 unpark
                                h.waitStatus != 0
                ) {
    // unpark AQS 中等待的线程, 进入 ㈡
                    unparkSuccessor(h);
                }
                return true;
            }
            return false;
        }
        // ㈠ Sync 继承过来的方法, 方便阅读, 放在此处
        protected final boolean tryRelease(int releases) {
    // state--
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
    // 支持锁重入, 只有 state 减为 0, 才释放成功
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }
        // ㈡ AQS 继承过来的方法, 方便阅读, 放在此处
        private void unparkSuccessor(Node node) {
    // 如果状态为 Node.SIGNAL 尝试重置状态为 0
    // 不成功也可以
            int ws = node.waitStatus;
            if (ws < 0) {
                compareAndSetWaitStatus(node, ws, 0);
            }
    // 找到需要 unpark 的节点, 但本节点从 AQS 队列中脱离, 是由唤醒节点完成的
            Node s = node.next;
    // 不考虑已取消的节点, 从 AQS 队列从后至前找到队列最前面需要 unpark 的节点
            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);
        }
    }
    
    

    可重入原理

    static final class NonfairSync extends Sync {
        // Sync 继承过来的方法, 方便阅读, 放在此处
        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()) {
    // state++
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
        // Sync 继承过来的方法, 方便阅读, 放在此处
        protected final boolean tryRelease(int releases) {
    // state--
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
    // 支持锁重入, 只有 state 减为 0, 才释放成功
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }
    }
    

    可打断原理之不可打断模式
    在此模式下,即使它被打断,仍会驻留在 AQS 队列中,一直要等到获得锁后方能得知自己被打断了

    static final class NonfairSync extends Sync {
        private final boolean parkAndCheckInterrupt() {
    // 如果打断标记已经是 true, 则 park 会失效
            LockSupport.park(this);
    // interrupted 会清除打断标记
            return Thread.interrupted();
        }
        final boolean acquireQueued(final Node node, int arg) {
            boolean failed = true;
            try {
                boolean interrupted = false;
                for (;;) {
                    final Node p = node.predecessor();
                    if (p == head && tryAcquire(arg)) {
                        setHead(node);
                        p.next = null;
                        failed = false;
    // 还是需要获得锁后, 才能返回打断状态
                        return interrupted;
                    }
                    if (
                            shouldParkAfterFailedAcquire(p, node) &&
                                    parkAndCheckInterrupt()
                    ) {
    // 如果是因为 interrupt 被唤醒, 返回打断状态为 true
                        interrupted = true;
                    }
                }
            } finally {
                if (failed)
                    cancelAcquire(node);
            }
        }
        public final void acquire(int arg) {
            if (
                    !tryAcquire(arg) &&
                            acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
            ) {
    // 如果打断状态为 true
                selfInterrupt();
            }
        }
        static void selfInterrupt() {
    // 重新产生一次中断
            Thread.currentThread().interrupt();
        }
    }
    

    可打断原理之可打断模式

    static final class NonfairSync extends Sync {
        public final void acquireInterruptibly(int arg) throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
    // 如果没有获得到锁, 进入 ㈠
            if (!tryAcquire(arg))
                doAcquireInterruptibly(arg);
        }
        // ㈠ 可打断的获取锁流程
        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()) {
    // 在 park 过程中如果被 interrupt 会进入此
    // 这时候抛出异常, 而不会再次进入 for (;;)
                        throw new InterruptedException();
                    }
                }
            } finally {
                if (failed)
                    cancelAcquire(node);
            }
        }
    }
    

    公平锁实现原理

    public class NonfairSync extends Sync {
        public final void acquireInterruptibly(int arg) throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
    // 如果没有获得到锁, 进入 ㈠
            if (!tryAcquire(arg))
                doAcquireInterruptibly(arg);
        }
        // ㈠ 可打断的获取锁流程
        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()) {
    // 在 park 过程中如果被 interrupt 会进入此
    // 这时候抛出异常, 而不会再次进入 for (;;)
                        throw new InterruptedException();
                    }
                }
            } finally {
                if (failed)
                    cancelAcquire(node);
            }
        }
    }
    
    

    注:lock的await/signal后面和LockSupport在一起研究

  • 相关阅读:
    Java 多个线程之间共享数据
    Mysql索引为什么要采用B+Tree而非B-Tree
    MyBatis常见面试题:通常一个Xml映射文件,都会写一个Dao接口与之对应,请问,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗?
    CentOS 8.1 基于二进制安装docker
    shell实现一键证书申请和颁发脚本
    配置DNS的主从以及实现域名反向解析
    利用Dockerfile实现nginx的部署
    编译安装Mariadb-10.5.5
    登录mysql出错:mysql: error while loading shared libraries: libtinfo.so.5: cannot open share
    一键安装MySQL5.7脚本
  • 原文地址:https://www.cnblogs.com/dalianpai/p/14204465.html
Copyright © 2020-2023  润新知