ReentrantLock概念
AQS队列同步器是锁的底层实现:最上层中:state:锁的状态 acquire()加锁 release()解锁
ReentrantLock是Java中的一个可重入的独占锁(互斥锁),它属于JDK级别的锁实现,与JVM级别的synchronized关键字在功能上有相似之处,但提供了更多的灵活性和控制能力。
比synchronized更加灵活,能自己设置什么时候定义锁,什么时候释放锁,ReentrantLock 提供了显式的 lock() 和 unlock() 方法,允许程序员在代码中明确指定何时获取和释放锁。
ReentrantLock 和 synchronized 都是可重入锁
ReentrantLock 又分为公平锁和非公平锁,默认使用非公平锁
一、基本概念
- 独占锁(互斥锁):每次只能有一个线程能持有锁,其他线程必须等待锁被释放后才能获取。
- 可重入性:ReentrantLock支持可重入性,即同一个线程在持有锁的情况下,可以多次获取该锁而不会导致死锁。每次成功获取锁后,锁的持有计数会增加;每次释放锁时,计数会减少,直到计数为0时锁才真正被释放。
ReentrantLock的公平锁和非公平锁
公平锁:按照线程在队列中的排队顺序,先到者先拿到锁
非公平锁:当线程要获取锁时,先通过两次 CAS 操作去抢锁,如果没抢到,当前线程加入到队列中等待唤醒。
ReentrantLock 默认采用非公平锁,因为考虑获得更好的性能,通过 boolean 来决定是否用公平锁(传入 true 用公平锁)。
下面介绍写ReentrantLock 的公平锁
static final class FairSync extends Sync { final void lock() { acquire(1); } // AbstractQueuedSynchronizer.acquire(int arg) public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); //判断当前是否加锁 if (c == 0) { // 1. 和非公平锁相比,这里多了一个判断:是否有线程在等待 //判断是否是第一个等待的,同时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; } }
核心逻辑就是:
1.首先根据根state判断是否加锁,没加锁进2,加锁了判断是否是当前线程加锁,是当前线程,那么state的值继续+1也就是重入锁(递归,或者同一线程下加锁方法对另一个加锁方法的调用)
2.判断是否是等待的第一个同时用cas尝试加锁,都为true时,对该方法加锁
下面介绍写ReentrantLock 的非公平锁
static final class NonfairSync extends Sync { final void lock() { // 2. 和公平锁相比,这里会直接先进行一次CAS,成功就返回了 if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } // AbstractQueuedSynchronizer.acquire(int arg) public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } } /** * Performs non-fair tryLock. tryAcquire is implemented in * subclasses, but both need nonfair try for trylock method. */ 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()) { int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
核心逻辑就是:
1.首先什么都不管,先进行一次CAS抢锁,失败进入2
2.首先根据根state判断是否加锁,没加锁进2,加锁了判断是否是当前线程加锁,是当前线程,那么state的值继续+1也就是重入锁(递归,或者同一线程下加锁方法对另一个加锁方法的调用)
2.判断不用判断是否是等待的第一个,直接用cas尝试加锁
总结:公平锁和非公平锁只有两处不同:
非公平锁在调用 lock 后,首先就会调用 CAS 进行一次抢锁,如果这个时候恰巧锁没有被占用,那么直接就获取到锁返回了。
非公平锁在 CAS 失败后,和公平锁一样都会进入到 tryAcquire 方法,在 tryAcquire 方法中,如果发现锁这个时候被释放了(state == 0),非公平锁会直接 CAS 抢锁,但是公平锁会判断等待队列是否有线程处于等待状态,如果有则不去抢锁,乖乖排到后面。
公平锁和非公平锁就这两点区别,如果这两次 CAS 都不成功,那么后面非公平锁和公平锁是一样的,都要进入到阻塞队列等待唤醒。
相对来说,非公平锁会有更好的性能,因为它的吞吐量比较大。当然,非公平锁让获取锁的时间变得更加不确定,可能会导致在阻塞队列中的线程长期处于饥饿状态。