ReentrantLock

3/23/2022 AQS

# 引言

可重入锁ReentrantLock的底层是通过AbstractQueuedSynchronizer实现,所以先要学习上一章节AbstractQueuedSynchronizer详解(https://www.yuque.com/hanchanmingqi-zjjw3/kb/fegupu)。

ReentrantLock 是基于 Lock 实现的可重入锁,所有的 Lock 都是基于 AQS 实现的,AQS 和 Condition 各自维护不同的对象,在使用 Lock 和 Condition 时,其实就是两个队列的互相移动。它所提供的共享锁、互斥锁都是基于对 state 的操作。而它的可重入是因为实现了同步器 Sync,在 Sync 的两个实现类中,包括了公平锁和非公平锁。

# ReentrantLock源码分析

# 类的继承关系

ReentrantLock实现了Lock接口,Lock接口中定义了lock与unlock相关操作,并且还存在newCondition方法,表示生成一个条件。

public class ReentrantLock implements Lock, java.io.Serializable

# 类的内部类

ReentrantLock总共有三个内部类,并且三个内部类是紧密相关的,下面先看三个类的关系。

img

说明: ReentrantLock类内部总共存在Sync、NonfairSync、FairSync三个类,NonfairSync与FairSync类继承自Sync类,Sync类继承自AbstractQueuedSynchronizer抽象类。下面逐个进行分析。

# Sync类

Sync类的源码如下:

abstract static class Sync extends AbstractQueuedSynchronizer {
    
    private static final long serialVersionUID = -5179523762034025860L;
    
    
    abstract void lock();
    
    // Sync内部类默认实现了非公平获取锁的方法
    final boolean nonfairTryAcquire(int acquires) {
        
        final Thread current = Thread.currentThread();
        
        int c = getState();
        if (c == 0) { // 状态为0表示处于资源未被占有状态
            if (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;
    }
    
    
    protected final boolean tryRelease(int releases) {
        int c = getState() - releases;
        if (Thread.currentThread() != getExclusiveOwnerThread()) 
            throw new IllegalMonitorStateException(); 
        
        boolean free = false; 
        if (c == 0) {
            free = true;
            
            setExclusiveOwnerThread(null); 
        }
        
        setState(c); 
        return free; 
    }
    
    
    protected final boolean isHeldExclusively() {
        
        
        return getExclusiveOwnerThread() == Thread.currentThread();
    }

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

    
    
    final Thread getOwner() {        
        return getState() == 0 ? null : getExclusiveOwnerThread();
    }
    
    final int getHoldCount() {            
        return isHeldExclusively() ? getState() : 0;
    }

    
    final boolean isLocked() {        
        return getState() != 0;
    }

    
    
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();
        setState(0); 
    }
}

Sync类存在如下方法和作用如下。

img

# NonfairSync类

NonfairSync类继承了Sync类,表示采用非公平策略获取锁,其实现了Sync类中抽象的lock方法,源码如下:

 static final class NonfairSync extends Sync {
    
    private static final long serialVersionUID = 7316153563782823691L;

    final void lock() {
        // 进入队列之前总是先尝试获取锁,而不会按照公平性原则进行等待,这也就是非公平的来源
        if (compareAndSetState(0, 1)) 
            setExclusiveOwnerThread(Thread.currentThread());
        else 
            acquire(1); // AQS类实现的方法
    }

    protected final boolean tryAcquire(int acquires) {
        // 调用NonfairSync的父类Sync已经实现的非公平获取锁的方法
        return nonfairTryAcquire(acquires);
    }
}

说明: 从lock方法的源码可知,每一次都尝试获取锁,而并不会按照公平等待的原则进行等待,让等待时间最久的线程获得锁

# FairSyn类

FairSync类也继承了Sync类,表示采用公平策略获取锁,其实现了Sync类中的抽象lock方法,源码如下:

 static final class FairSync extends Sync {
    
    private static final long serialVersionUID = -3000897897090466540L;

    final void lock() {
        
        acquire(1);// AQS实现的方法
    }

    protected final boolean tryAcquire(int acquires) {
        
        final Thread current = Thread.currentThread();
        
        int c = getState();
        if (c == 0) { 
            // 公平锁在设置状态之前先判断队列中是否有在等待的线程
            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;
    }
}

说明: 跟踪lock方法的源码可知,当资源空闲时,它总是会先判断sync队列(AbstractQueuedSynchronizer中的数据结构)是否有等待时间更长的线程(通过**!hasQueuedPredecessors()**这条语句来实现),如果存在,则将该线程加入到等待队列的尾部,实现了公平获取原则。其中,FairSync类的lock的方法调用如下,只给出了主要的方法。

img

说明: 可以看出只要资源被其他线程占用,该线程就会添加到sync queue中的尾部,而不会先尝试获取资源。这也是和Nonfair最大的区别,Nonfair每一次都会尝试去获取资源,如果此时该资源恰好被释放,则会被当前线程获取,这就造成了不公平的现象,当获取不成功,再加入队列尾部

# 类的属性

ReentrantLock类的sync非常重要,对ReentrantLock类的操作大部分都直接转化为对Sync和AbstractQueuedSynchronizer类的操作。

public class ReentrantLock implements Lock, java.io.Serializable {
    
    private static final long serialVersionUID = 7373984872572414699L;    
    
    private final Sync sync;
}

# 类的构造函数

  • ReentrantLock()型构造函数

默认是采用的非公平策略获取锁

public ReentrantLock() {
    
    sync = new NonfairSync();
}
  • ReentrantLock(boolean)型构造函数

可以传递参数确定采用公平策略或者是非公平策略,参数为true表示公平策略,否则,采用非公平策略:

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

# 核心函数分析

通过分析ReentrantLock的源码,可知对其操作都转化为对Sync对象的操作,由于Sync继承了AQS,所以基本上都可以转化为对AQS的操作。如将ReentrantLock的lock函数转化为对Sync的lock函数的调用,而具体会根据采用的策略(如公平策略或者非公平策略)的不同而调用到Sync的不同子类。

所以可知,在ReentrantLock的背后,是AQS对其服务提供了支持,由于之前我们分析AQS的核心源码,遂不再累赘。下面还是通过例子来更进一步分析源码。

# 示例分析

# 公平锁

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class MyThread extends Thread {
    private Lock lock;
    public MyThread(String name, Lock lock) {
        super(name);
        this.lock = lock;
    }
    
    public void run () {
        lock.lock();
        try {
            System.out.println(Thread.currentThread() + " running");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } finally {
            lock.unlock();
        }
    }
}

public class AbstractQueuedSynchonizerDemo {
    public static void main(String[] args) throws InterruptedException {
        Lock lock = new ReentrantLock(true);
        
        MyThread t1 = new MyThread("t1", lock);        
        MyThread t2 = new MyThread("t2", lock);
        MyThread t3 = new MyThread("t3", lock);
        t1.start();
        t2.start();    
        t3.start();
    }
}

运行结果(某一次):

Thread[t1,5,main] running
Thread[t2,5,main] running
Thread[t3,5,main] running

说明: 该示例使用的是公平策略,由结果可知,可能会存在如下一种时序。

img

说明: 首先,t1线程的lock操作 -> t2线程的lock操作 -> t3线程的lock操作 -> t1线程的unlock操作 -> t2线程的unlock操作 -> t3线程的unlock操作。根据这个时序图来进一步分析源码的工作流程。

  • t1线程执行lock.lock,下图给出了方法调用中的主要方法。

img

说明: 由调用流程可知,t1线程成功获取了资源,可以继续执行。

  • t2线程执行lock.lock,下图给出了方法调用中的主要方法。

img

说明: 由上图可知,最后的结果是t2线程会被禁止,因为调用了LockSupport.park。

  • t3线程执行lock.lock,下图给出了方法调用中的主要方法。

img

说明: 由上图可知,最后的结果是t3线程会被禁止,因为调用了LockSupport.park。

  • t1线程调用了lock.unlock,下图给出了方法调用中的主要方法。

img

说明: 如上图所示,最后,head的状态会变为0,t2线程会被unpark,即t2线程可以继续运行。此时t3线程还是被禁止。

  • t2获得cpu资源,继续运行,由于t2之前被park了,现在需要恢复之前的状态,下图给出了方法调用中的主要方法。

img

说明: 在setHead函数中会将head设置为之前head的下一个结点,并且将pre域与thread域都设置为null,在acquireQueued返回之前,sync queue就只有两个结点了。

  • t2执行lock.unlock,下图给出了方法调用中的主要方法。

img

说明: 由上图可知,最终unpark t3线程,让t3线程可以继续运行。

  • t3线程获取cpu资源,恢复之前的状态,继续运行。

img

说明: 最终达到的状态是sync queue中只剩下了一个结点,并且该节点除了状态为0外,其余均为null。

  • t3执行lock.unlock,下图给出了方法调用中的主要方法。

img

说明: 最后的状态和之前的状态是一样的,队列中有一个空节点,头节点为尾节点均指向它。

使用公平策略和Condition的情况可以参考上一篇关于AQS的源码示例分析部分,不再累赘。

# 参考

JUC锁: ReentrantLock详解 | Java 全栈知识体系 (opens new window) JUC锁: ReentrantLock详解 | Java 全栈知识体系 (opens new window)

Last Updated: 3/28/2022, 9:29:49 PM