ReentrantLock源码解析

本文深入解析ReentrantLock的实现原理,包括其核心属性Sync的作用、非公平锁与公平锁的区别及加锁流程,并探讨了资源的释放过程。

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

ReentrantLock源码解析

一、简介

ReentrantLock是一个轻量级锁,主要采用同步队列,以及CAS去进行资源的争夺,包含一个重要的属性Sync,会依靠这个Sync来进行资源争夺,同时这个Sync是继承了AQS的,推荐看这个之前先看AQS

重要内部类
Sync
	//重要,我们的ReentrantLock的加锁解锁基本靠这个
 	abstract static class Sync extends AbstractQueuedSynchronizer {
 		//加锁方法,被abstract修饰,需要子类实现
		abstract void lock();

		//不公平的尝试获取锁,final修饰,代表不能被重写
		final boolean nonfairTryAcquire(){...}
	
		//尝试释放占有的资源,被final修饰,不可重写,说明在释放state,公平锁和非公平锁都是一样的
		protected final boolean tryRelease(int releases) {...}

Sync.nonfairAcquire()

	final boolean nonfairTryAcquire(int acquires) {
            //获取当前线程
            final Thread current = Thread.currentThread();
            //获取state变量的值,也就是当前锁的被重入的次数
            int c = getState();
            //state为0,说明当前锁未被任何线程持有
            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.tryRelease()方法

		protected final boolean tryRelease(int releases) {
			//计算我们的state-我们需要释放的大小,代表释放成功后,我们的release需要CAS设置的值
            int c = getState() - releases;
            //如果不是自己的线程占有的,需要抛出异常
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            //如果释放自己的资源后,state是0的话,需要设置占有线程是null,方便其他的线程去占有资源
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            //修改state
            setState(c);
            //返回是否释放成功
            return free;
        }
	}

上面介绍了Sync的全部方法,接下来看他的子类实现NonfairSync和FairSync两个,分别代表不公平和公平的,两者加锁流程会有不一样

NonfairSync
	static final class NonfairSync extends ReentrantLock.Sync {
		//非公平锁加锁流程,被final修饰,不可重写
		final void lock() {...}
		//非公平锁尝试获取锁的方法
        protected final boolean tryAcquire(int acquires) {...}
	}

NonfairSync.lock()

		//非公平锁的加锁方式
		final void lock() {
            //直接尝试去CAS修改我们的state,这个就是体现非公平了,因为作为对比,公平锁在一开始并不会去尝试CAS修改我们的state
            //公平锁一开始只会有一次尝试CAS,失败后是直接去加入队列,而非公平锁在一开始会尝试2次去占有锁,这里是第一次,之后还会有一次
            if (compareAndSetState(0, 1))
                //成功的话就会修改占有线程
                setExclusiveOwnerThread(Thread.currentThread());
            else
                //调用到父类的父类,也就是AQS的acquire方法,AQS定义了一套获取锁的算法逻辑,具体实现交给子类实现,但用模板设计模式
                //给出了大概思路,回忆一下,AQS.acquire会调用tryAcquire(),addWaiter(),acquireQueued()三个重要方法,
                //tryAcquire是交给了子类实现
                acquire(1);
        }

		//我们的非公平锁的tryAcquire()方法,调用我们的父类Sync.nonfairTryAcquire()去了
		//在父类的方法里面会判断state是不是0,然后继续尝试CAS一次,所以在入队以前,非公平锁会有两次直接尝试获取的时间
		//如果都失败了,就会开始进行入队操作,这个是在AQS的逻辑中了,建议先看完AQS再来看reentrantLock
		protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }

上面就是非公平锁的加锁过程,释放过程是在我们的Sync中定义好了

这里就是我们的公平锁的逻辑了

FairSync
	static final class FairSync extends ReentrantLock.Sync {
		final void lock() {...}
		protected final boolean tryAcquire(int acquires) {...}
	}

FairSync.lock()

	final void lock() {
		//直接调用AQS.acquire(),非公平锁在这里会先进行一次CAS,公平锁不会
       	acquire(1);
   	}

FairSync.lock()

		//公平锁的加锁逻辑,基本和非公平锁一样,不过多了一个地方!hasQueuedPredecessors(),也是不同
		protected final boolean tryAcquire(int acquires) {
            //获取到当前线程
            final Thread current = Thread.currentThread();
            //获取到state
            int c = getState();
            //如果没有人占领
            if (c == 0) {
                //尝试CAS修改
                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;
        }
	//主要就是判断我们的队列的情况,看自己能不能进行一次直接CAS
	public final boolean hasQueuedPredecessors() {
        Node t = tail; 
        Node h = head;
        Node s;
		
		//因为想要去继续CAS,这里必须返回false,如果满足了,就不能进行CAS
        //如果头尾相等,也就是同步等待队列里面没有其他线程,返回的就是false,配合!,就是true,然后就可以去进行CAS我们的state
        //因为只有自己,所以不需要去排队
        //头尾不相等,代表已经有其他的下刹那哼,如果head的下一个节点是空的,或者这个节点不是自己的当前的这个线程,就会返回false
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

ReentrantLock
然后看我们的reentrantLock怎么使用这个加锁,以及加锁流程是怎样的

ReentrantLock.lock()加锁方法

	public void lock() {
		//直接调用sync.lock(),这个sync是什么时候
        sync.lock();
    }

ReentrantLock.unlock()

	public void unlock() {
        sync.release(1);
    }

这个sync是什么时候初始化的呢
在构造函数进行初始化,默认是非公平的,支持自定义,传入true是公平锁,false是非公平锁

	public ReentrantLock() {
        sync = new ReentrantLock.NonfairSync();
    }


    public ReentrantLock(boolean fair) {
        sync = fair ? new ReentrantLock.FairSync() : new ReentrantLock.NonfairSync();
    }
总结

非公平锁:
- 调用reentrantLock.lock()
- 先去执行到我们的sync.lock()
- 尝试先去进行一次CAS占有锁,成功直接返回
- 失败就会调用AQS.acquire(),分别执行tryAcquire()尝试获取锁资源,addWaiter()添加入队列,acquireQueued()进行阻塞直到获取锁
- 先执行到nonFairSync.tryAcquire(),然后执行到Sync.nonfairTryAcquire()
- 判断state状态是不是为0,是的话继续进行一次CAS占有锁
- 判断占有线程是不是自己,是的话直接将需要的大小加到state上去
- 否则占有不成功,需要执行AQS的另外两个方法进入队列,然后等待获取锁

公平锁
- 调用reentrantLock.lock()
- 先去执行到我们的sync.lock()
- 直接调用AQS.acquire(),分别执行tryAcquire()尝试获取锁资源,addWaiter()添加入队列,acquireQueued()进行阻塞直到获取锁
- 调用fairAcquire.tryAcquire(),
- 然后先判断state是不是没人占领,是的话会先判断队列情况,例如是不是没有其他线程,占有线程是不是自己之类的
- 然后尝试CAS,成功直接设置占有线程然后返回
- 如果是自己占领的,就是直接重入
- 如果占有不成功,就会需要执行AQS的另外两个方法进入队列,然后等待获取锁

释放锁逻辑,非公平锁和公平锁释放都一样,
- 全部都是先计算state,
- 如果已经等于0了,就会设置占有线程为null
- 然后CASstate为我们的计算值
- 然后返回是否释放成功

**注意:**我开始在前面说的非公平锁两次CAS,公平锁一次CAS,其实严格意义上来说,除了非公平锁一开始的CAS外,其他的都是事先需要判断state是不是等于0的,不为0就不会进行一次CAS,所以说的两次是值有两次抢占锁的机会,不一定会进行CAS抢占

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值