[JUC source code] ReentrantLock source code analysis (fair lock and unfair lock)

The information that can be obtained from class annotations are:

  1. Reentrant mutex locks have the same functional semantics as synchronized locks, but are more extensible;
  2. The constructor accepts the parameter of fairness. When fairness is true, the order of acquiring the lock is guaranteed, false does not guarantee;
  3. The throughput of fair locks is low, and the fairness of obtaining locks cannot represent the fairness of thread scheduling;
  4. The tryLock() method without parameters does not follow fairness and is unfair (lock and unlock are both fair and unfair, while tryLock only has a fair lock, so let’s talk about it separately).

To add to the second point, the fairness and unfairness of ReentrantLock are for obtaining locks. If it is fair, it can ensure that threads in the synchronization queue obtain locks in order from beginning to end. Unfairness cannot be guaranteed. In the process of releasing the lock, we have no fair or unfair argument

1. Structure

Reentrantlock inheritance relationship, core member variables and main constructor:

public class ReentrantLock implements Lock, java.io.Serializable {
    
    
    
    // Sync 同步器提供了所有的加锁,释放锁的机制
    private final Sync sync;
    
    // Sync 继承了 AQS,使用了 AQS 的 state 字段代表当前锁的计算
    abstract static class Sync extends AbstractQueuedSynchronizer{
    
    ...}   
    // 非公平锁,继承了Sync
    static final class NonfairSync extends Sync{
    
    ...}        
    // 公平锁,继承了Sync
    static final class FairSync extends Sync{
    
    ...}
    
    //------------------------------构造函数-------------------------------------
    // 无参数构造器,即默认是非公平锁
    public ReentrantLock() {
    
    
        sync = new NonfairSync();
    }
	
	// 根据传入的参数决定是公平锁还是非公平锁
    public ReentrantLock(boolean fair) {
    
    
        sync = fair ? new FairSync() : new NonfairSync();
    }
}

1.1 Lock interface

Lock interface, which defines the lock related methods

public interface Lock {
    
    
    // 上锁 
    void lock();
    // 可中断锁
    void lockInterruptibly() throws InterruptedException;
    
    // 尝试获取锁
    boolean tryLock();
    // 设定尝试时间,过期返回
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    
    // 释放锁
    void unlock();
    
    // 创建条件队列
    Condition newCondition();
}

1.2 Sync

Sync directly inherits AQS and provides the necessary methods regardless of fairness/unfairness locks. Such as lock state related methods, lock release, acquisition condition queue, etc.

abstract static class Sync extends AbstractQueuedSynchronizer {
    
    
        private static final long serialVersionUID = -5179523762034025860L;
		
		// 给子类(FairSync,NonfairSync)实现
        abstract void lock();
        
        //.....
}		

1.2.1 state related methods

The basic state field of the lock, directly call the AQS method

 // 锁是否被持有
final boolean isLocked() {
    
    
    return getState() != 0;
}
// 是否是当前线程持有锁
protected final boolean isHeldExclusively() {
    
    
    // 该方法在AbstractOwnableSynchronizer中定义
    return getExclusiveOwnerThread() == Thread.currentThread();
}		
// 持有锁的线程是谁
final Thread getOwner() {
    
    
    return getState() == 0 ? null : getExclusiveOwnerThread();
}
// 线程持有同一个锁的数量
final int getHoldCount() {
    
    
    return isHeldExclusively() ? getState() : 0;
}

1.2.2 nonfairTryAcquire()

Attempts to obtain unfair locks are divided into the following three situations:

  • The lock is not occupied, try to get the lock by yourself
  • The lock has been acquired by oneself, reentrant, state+acquire (with upper limit)
  • Failed to get the lock, enter the synchronization queue

One thing to note here is that the tryAcquire methods of fair locks and unfair locks are different. The tryAcquire of non-fair locks is in NonfairSync, but the core logic (nonfairTryAcquire) is implemented in Sync, while the tryAcquire of fair locks is completely implemented in FairSync. .

final boolean nonfairTryAcquire(int acquires) {
    
    
    final Thread current = Thread.currentThread();
    // 获取锁状态
    int c = getState();
    // 同步器的状态是 0 表示同步器的锁没有人持有
    if (c == 0) {
    
    
        // 当前线程,尝试获取锁(CAS修改state)
        if (compareAndSetState(0, acquires)) {
    
    
            // 修改成功,拿到锁,标记当前持有锁的线程是谁
            setExclusiveOwnerThread(current);
            return true;
        }
     }
    // 锁已经被占有,且刚好是当前线程,这里表重入
    else if (current == getExclusiveOwnerThread()) {
    
    
        // 体现可重入性。当前线程持有锁的数量 + acquires
        int nextc = c + acquires;
        // int 是有最大值的,<0 表示持有锁的数量超过了 int 的最大值
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        // 重新设置state,该放在AQS中定义
        setState(nextc);
        return true;
    }
    // 拿锁失败,线程进入同步队列
    return false;
}

From this method, we can also see that Reentrantlock is reentrant . Reentrancy means that threads can repeatedly lock shared resources, and correspondingly, they can be released repeatedly when they are released.

  • For ReentrantLock, when the lock is acquired, the state will increase by 1, and when the lock is repeatedly acquired, the state can be continuously incremented. For example, the current state is 4, which means that the thread has locked the shared resource 4 times
  • Each time the thread releases the lock of the shared resource, the state will be decremented by 1, and the shared resource will not be released until the decrement reaches 0.

1.2.3 tryRelease()

Attempts to release the lock, both unfair and fair lock release are this method. There are also three situations:

  • The current thread does not hold the lock, and an error is reported
  • state=0, set owner to null, which means the release is successful
  • state!=0, which means it is a reentrant lock and has not been released, return false
 protected final boolean tryRelease(int releases) {
    
    
    // 当前同步器的状态减去释放的个数,参数releases一般为 1
    int c = getState() - releases;
    // 当前线程根本都不持有锁,报错
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    // 如果 c 为 0,表示当前线程持有的锁都释放了
    if (c == 0) {
    
    
        free = true;
        setExclusiveOwnerThread(null);
    }
    // 如果 c 不为 0,那么就是可重入锁,并且锁没有释放完,用 state 减去 releases 即可,无需做其他操作
    setState(c);
    return free;
}

1.2.4 newCondition ()

Create a conditional queue

final ConditionObject newCondition() {
    
    
    return new ConditionObject();
}

Let's take a look at the difference between the non-fair lock NonFairSync and the fair lock FairSync, what is fair and what is unfair.

1.3 Non-fair lock: NonfairSync

The unfairness of unfair locks is reflected in two places:

  1. The thread has a chance to acquire the lock without entering the synchronization queue: in the lock method and tryAcquire method a total of 2 attempts to modify the state to obtain the lock
  2. The thread has entered the synchronization queue and the non-queue has a chance to acquire the lock: tryAcquire does not verify the position of the current thread in the synchronization queue (compare the tryAcquire method of FairSync below), and everyone has the opportunity to modify the state to obtain the lock.

To put it more directly, a new thread or a short blocking time may run before a long blocked thread.

	static final class NonfairSync extends Sync {
    
    
        private static final long serialVersionUID = 7316153563782823691L;
		
		// 获取非公平锁
        final void lock() {
    
    
        	// cas给state赋值
            if (compareAndSetState(0, 1))
            	// cas赋值成功,代表拿到当前锁,记录拿到锁的线程
                setExclusiveOwnerThread(Thread.currentThread());
            else
            	// acquire是AQS的方法,会再一次尝试获得锁,若还失败就会进入到同步队列中
                acquire(1);
        }
		
		// 尝试获取非公平锁。该方法会在lock拿锁失败后,在acquire方法中调用
        protected final boolean tryAcquire(int acquires) {
    
    
        	// 直接调用父类Sync的nonfairTryAcquire方法
            return nonfairTryAcquire(acquires);
        }
    }

1.4 Fair Lock: FairSync

The fairness of fairness lock is reflected in:

  1. The new thread must enter the synchronization queue and receive the synchronizer schedule
  2. In the synchronous queue, only the second thread of the queue has the opportunity to run

To put it more directly, regardless of the new and old threads, they must follow the FIFO who blocks first and runs first.

	static final class FairSync extends Sync {
    
    
        private static final long serialVersionUID = -3000897897090466540L;
		
		// 获取公平锁
		// 相较于非公平锁的lock方法,没了一进来就尝试修改state
        final void lock() {
    
    
        	// acquire 是 AQS 的方法,表示先尝试获得锁,失败之后进入同步队列阻塞等待
            acquire(1);
        }
		
		// 尝试获取非公平锁
		// 相较于非公平锁的nonfairTryAcquire,这里是必须进入了同步队列才有机会修改state拿锁
        protected final boolean tryAcquire(int acquires) {
    
    
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
    
    
            	// hasQueuedPredecessors 是实现公平的关键!!
		        // 它会判断当前线程是不是属于同步队列的头节点的下一个节点(头节点是释放锁的节点)
		        // 如果是(返回false),符合先进先出的原则,可以获得锁
		        // 如果不是(返回true),则继续等待
                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;
        }
    }

Here is a summary. Fairness and unfairness refer to the mechanism by which threads get the lock (lock & tyrAcquire), and there is no difference when the lock is released (tryRelease). If the threads in the synchronization queue get the lock in the order of blocking, we call it fair, and vice versa.

The underlying implementation of fairness is implemented in the tryAcquire method of ReentrantLock (called the hasQueuedPredecessors method of AQS). When the node of the synchronization queue is to be released (or when the lock is acquired), it is judged whether the current thread node is the latter of the head node of the synchronization queue. The node is released if it is, and it cannot be released if it is not. Through this mechanism, it is guaranteed that the threads in the synchronization queue are locked in order from beginning to end.

2. Method & api

The methods in Reentrantlock are actually nothing to say, because they are all based on the AQS framework, which is specifically embodied here as calling Sync directly

2.1 Locking: lock

public void lock() {
    
    
    sync.lock();
}

2.2 Try to get the lock: tryLock

// 无参构造器
public boolean tryLock() {
    
    
    return sync.nonfairTryAcquire(1);
}
// timeout 为超时的时间,在时间内,仍没有得到锁,会返回 false
public boolean tryLock(long timeout, TimeUnit unit)
        throws InterruptedException {
    
    
    return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}

2.3 Release the lock: unlock

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

2.4 Condition queue: newCondition

 public Condition newCondition() {
    
    
        return sync.newCondition();
}

3.The difference between Reentrantlock and synchronized

  • Synchronized is implemented at the JVM level; ReentrantLock is implemented at the JDK code level
    • Sync is directly linked to calling the os function to achieve blocking before optimization, but after optimization: volatile + spin + CAS achieves thread safety in user mode
    • Reentrantlock realizes thread control at the code level (volatile+spin+CAS) through AQS, ensuring thread safety
  • synchronized After the execution of the locked code block or an exception occurs, the lock is automatically released; ReentrantLock will not automatically release the lock, and the release needs to be displayed in the finally{} code block
  • Synchronized competition locks will always wait; ReentrantLock can try to acquire the lock and get the acquisition result (tryLock)
  • Synchronized acquisition of the lock cannot set the timeout; ReentrantLock can set the timeout period for acquiring the lock (tryLock(timeUnit.S)
  • Synchronized cannot achieve fair lock; ReentrantLock can satisfy fair lock, that is, wait for the lock to be acquired first (new Reentrantlock(true))
  • Synchronized control waiting and wake-up need to be combined with wait() and notify() and notifyAll() of the locked object; ReentrantLock control waiting and wake-up need to be combined with Condition's await() and signal(), signalAll() methods (newCondition)

Guess you like

Origin blog.csdn.net/weixin_43935927/article/details/108721781