ReentrantLock 原理

本文详细介绍了Java中ReentrantLock的非公平锁实现原理,包括尝试使用CAS操作获取锁,线程阻塞与唤醒机制。同时,深入探讨了条件变量的await和signal方法,展示了线程等待与唤醒的流程。
摘要由CSDN通过智能技术生成

非公平锁实现原理

ReentrantLock 默认是非公平锁

    public ReentrantLock() {
        sync = new NonfairSync();
    }
  • 没有竞争时,就一个线程,加锁成功
    在这里插入图片描述
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
        final void lock() {
        	// 尝试用 compareAndSet 来修改状态,0 表示未加锁,1 表示加锁
            if (compareAndSetState(0, 1))
            	// 修改成功,把 OwnerThread 改为 currentThread
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }
    public final void acquire(int arg) {
    	// 也会尝试去获取以下锁,tryAcquire 为 true 获取到锁就不会执行 if 了
        if (!tryAcquire(arg) &&
        	// tryAcquire 为 false,尝试创建一个 Node 节点对象加入到等待队列中
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
  • 第一个竞争出现时,State状态已经为1,compareAndSetState(0, 1) 失败,进入 else 分支点进 acquire 方法
    在这里插入图片描述
    在这里插入图片描述
    (其中第一个 Node 称为 Dummy(哑元)或哨兵,用来占位,并不关联线程)

  • 当前线程进入 acquireQueued 逻辑

    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)) { // 尝试获取锁,获取锁失败就进入下一个if
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                // shouldParkAfterFailedAcquire 获取锁失败返回 true 就阻塞,返回 false 继续循环尝试
                if (shouldParkAfterFailedAcquire(p, node) && 
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

进入 shouldParkAfterFailedAcquire 逻辑,将前驱 node,即 head 的 waitStatus 状态改为 -1,-1表示它有责任唤醒它的后继节点
在这里插入图片描述
shouldParkAfterFailedAcquire 执行完毕回到 acquireQueued ,再次 tryAcquire 尝试获取锁,当然这时state 仍为 1,失败

当再次进入 shouldParkAfterFailedAcquire 时,这时因为其前驱 node 的 waitStatus 已经是 -1,这次返回 true

进入 parkAndCheckInterrupt, Thread-1 park(灰色表示),这时候 Thread-1 就阻塞了
在这里插入图片描述

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

释放锁流程

    public void unlock() {
        sync.release(1);
    }
    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0) // 满足条件,唤醒下一个节点
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
  • Thread-0 释放锁,进入 tryRelease 流程,如果成功设置 exclusiveOwnerThread 为 null,state = 0
    在这里插入图片描述

当前队列不为 null,并且 head 的 waitStatus = -1,进入 unparkSuccessor 流程

找到队列中离 head 最近的一个 Node(没取消的),unpark 恢复其运行,本例中即为 Thread-1

回到 Thread-1 的 acquireQueued 流程

在这里插入图片描述
如果加锁成功(没有竞争),会设置

exclusiveOwnerThread 为 Thread-1,state = 1

head 指向刚刚 Thread-1 所在的 Node,该 Node 清空 Thread

原本的 head 因为从链表断开,而可被垃圾回收

  • 如果这时候有其它线程来竞争(非公平的体现),例如这时有 Thread-4 来了
    在这里插入图片描述

如果不巧又被 Thread-4 占了先

Thread-4 被设置为 exclusiveOwnerThread,state = 1

Thread-1 再次进入 acquireQueued 流程,获取锁失败,重新进入 park 阻塞

条件变量实现原理

每个条件变量其实就对应着一个等待队列,其实现类是 ConditionObject

await 流程

        public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            // 当调用 await() 方法就会进入这个,这个流程就是把线程加入到条件变量的链表里面去
            Node node = addConditionWaiter(); 
            // 有可能是锁重入,把锁都释放掉
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {
            	// 当前线程 park
                LockSupport.park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }

开始 Thread-0 持有锁,调用 await,进入 ConditionObject 的 addConditionWaiter 流程

创建新的 Node 状态为 -2(Node.CONDITION)表示等待,关联 Thread-0,加入等待队列尾部
在这里插入图片描述
接下来进入 AQS 的 fullyRelease 流程,释放同步器上的锁
在这里插入图片描述
unpark AQS 队列中的下一个节点,竞争锁,假设没有其他竞争线程,那么 Thread-1 竞争成功

在这里插入图片描述
signal 流程

        public final void signal() {
        	// 判断是否是锁的持有线程来唤醒我
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            // 唤醒第一个
            Node first = firstWaiter;
            if (first != null)
                doSignal(first);
        }
        private void doSignal(Node first) {
            do {
            	// 从等待链表中移除掉
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                first.nextWaiter = null;
            // transferForSignal 意思是从条件等待队列转移到等待队列中,能否成功
            } while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);
        }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值