java编程--ReentrantLock详解

本文深入探讨了ReentrantLock的工作原理,包括非公平锁的lock方法实现,与synchronized关键字的区别,以及解锁流程。详细解释了NonfairSync的lock方法如何使用CAS尝试获取锁,acquire方法在获取锁失败时如何将线程加入AQS队列,以及unlock方法如何释放锁并唤醒等待线程。

非公平锁的lock方法

在这里插入图片描述

1.当我们调用ReentrantLock的lock方法的时候,实际上是调用了NonfairSync的lock方法,这个方法先用CAS操作,去尝试抢占该锁。如果成功,就把当前线程设置在这个锁上,表示抢占成功。如果失败,则调用acquire模板方法,等待抢占。

 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() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

2.其中acquire的原理是尝试获取锁,如果没有获取成功,就在AQS队列中增加一个当前线程的结点,表示等待抢占。在进入队列时会去再次执行一次获取锁操作,如果没有获取就挂起等待唤醒。
tryAcquire(arg)方法就是尝试再次获取锁,先比较当前锁的状态是否是0,如果是0,则尝试去原子抢占这个锁,如果当前锁的状态不是0,就去比较当前线程和占用锁的线程是不是一个线程,如果是,会去增加状态变量的值,其次返回false。

 public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

3.结点加入队列,进入acquireQueued方法,外层是一个for循环,如果当前结点是头结点的下个结点,再通过tryAcquire尝试获取锁,如果获得,说明头结点已经释放锁,这时将当前结点设置为头结点。将上一个结点的next设置为null。如果本次循环没有获取到锁,则进入shouldparkAfterFailedAcquire方法,此方法会判断当前线程是否挂起,如果前一个结点已经是SIGNAL状态,则需要挂起,如果前一个结点是取消状态,则删除结点。如果是其他状态,则尝试设置成SIGNAL状态,并返回不需要挂起,进行第二次抢占。完成上面后进入挂起状态。


    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; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        if (ws > 0) {
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * waitStatus must be 0 or PROPAGATE.  Indicate that we
             * need a signal, but don't park yet.  Caller will need to
             * retry to make sure it cannot acquire before parking.
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

unlock方法详细描述
1、调用unlock方法,其实是直接调用AbstractQueuedSynchronizer的release操作。

2、进入release方法,内部先尝试tryRelease操作,主要是去除锁的独占线程,然后将状态减一,这里减一主要是考虑到可重入锁可能自身会多次占用锁,只有当状态变成0,才表示完全释放了锁。

3、一旦tryRelease成功,则将CHL队列的头节点的状态设置为0,然后唤醒下一个非取消的节点线程。

4、一旦下一个节点的线程被唤醒,被唤醒的线程就会进入acquireQueued代码流程中,去获取锁。

 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;
    }

synchronized 和 ReentrantLock 区别

参考博文:https://blog.csdn.net/meism5/article/details/90413901
1.synchronized 竞争锁时会一直等待;ReentrantLock 可以尝试获取锁,并得到获取结果
2.synchronized 获取锁无法设置超时;ReentrantLock 可以设置获取锁的超时时间
3.synchronized 无法实现公平锁;ReentrantLock 可以满足公平锁,即先等待先获取到锁
4.synchronized 控制等待和唤醒需要结合加锁对象的 wait() 和 notify()、notifyAll();ReentrantLock 控制等待和唤醒需要结合 Condition 的 await() 和 signal()、signalAll() 方法
5.synchronized 是 JVM 层面实现的;ReentrantLock 是 JDK 代码层面实现
6.synchronized 在加锁代码块执行完或者出现异常,自动释放锁;ReentrantLock 不会自动释放锁,需要在 finally{} 代码块显示释放

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值