Java并发编程(四)—基于队列同步器的可重入锁

本文详细分析了ReentrantLock的工作原理,包括其可重入特性、公平锁与非公平锁的实现方式。ReentrantLock作为Java提供的高级锁机制,允许线程在获取锁后重复加锁,支持公平性和非公平性获取策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

在Java 1.5版本的时候,Java提供了新的同步机制—Lock接口,通过Lock接口可以实现不同类型的锁来支持多线程的同步。相比较synchronized关键字,通过Lock实现的锁可以更好的控制同步块的粒度。 在Lock子类的内部实现中,锁是通过Java的队列同步器实现的(AbstractQueuedSynchronizer),不熟悉队列同步器的同学,可以参考我的另一篇博客[Java并发编程(三)—队列同步器(AbstractQueuedSynchronizer)](https://blog.youkuaiyun.com/programerxiaoer/article/details/81463219)

关于ReentrantLock的原理分析

ReentrantLock是Lock接口的一个子类,它是一种可重入锁,也就表示持有该锁的线程可以对资源重复加锁。除此之外,可重入锁也支持公平锁和非公平锁。
ReentrantLock的可重入特性
可重入是指的线程在获取锁之后能够再次获取到该锁并且不会阻塞。我们都知道在队列同步器通过模板方法tryAcquire获取锁,并且通过一个int类型的状态标志来标识锁是否获取成功,而实现可重入的特性就需要依赖状态标志。
final Thread current = Thread.currentThread();
            int c = getState();  //状态标志
            if (c == 0) {  //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;

以上就是ReentrantLock的可重入特性的原理实现。通过状态标志,当状态标志为0时,表示当前线程还未获取锁,然后尝试获取锁。如果是当前线程在此获取锁时,更新状态标志。
我们可以看到只有状态标志为0时,线程才能尝试获取锁,那么可重入锁在获取锁n次后,相应的也需要释放n次所。

 protected final boolean tryRelease(int releases) {
            int c = getState() - releases; // 释放锁
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) { //状态标志为0时,释放锁成功
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }
ReentrantLock的公平锁与非公平锁
ReentrantLock还分为公平锁和非公平锁。其中公平锁就是线程获取锁的顺序遵循FIFO,也就是在等待队列中,线程安装先进先出的规则获取锁。而非公平锁只要队列中的线程能够设置同步状态成功就可以获取锁,也就是线程获取锁时根据CPU调度的。所以非公平锁可能导致线程“饥饿”问题,是的其他线程能够得到的时间片较少。但是使用非公平锁时,线程的切换次数要少于使用公平锁,所以效率也相应较高。
公平锁的实现
ReentrantLock中公平锁也是通过队列同步器提供的模板方法实现锁机制。
static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;
        /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        @ReservedStackAccess
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
            	// 这里就是实现公平锁的关键
            	//hasQueuedPredecessors()的作用就是判断当前线程在队列中是否有前置节点
            	//没有的情况下才能尝试获取锁
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }
非公平锁的实现
final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
            	//可以看到非公平锁与公平锁的差别只是没有了hasQueuedPredecessors()的判断条件
                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;
        }

从上面的代码可以看出,在代码实现上公平锁与非公平锁的差别只是判断了尝试获取锁的线程在队列中是否有前置节点。如果有的话,在公平锁的情况下就不能获取锁,非公平锁的情况下可以获取锁。

总结

通过以上的分析,可以看到锁(Lock)的实现并不复杂。这是因为Java提供了队列同步器,在队列同步器中实现了锁获取同步状态的机制,不同特性的锁只需要实现相应的模板方法即可,同时实现相应的获取与释放逻辑就可以完成锁的实现。这也开发者提供了更多的选择,让开发者也能够轻松的实现自己的锁。 除了Lock的实现,一些同步工具类也是基于队列同步器实现的,比如:CountDownLatch、CyclicBarrier等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值