Lock框架之ReentrantLock



ReentrantLock类看名字叫可重入锁,具体的功能实现我们结合代码解读

ReentrantLock结构解读

实现接口Lock

研究一个类要从该类的结构入手,从一个类的继承实现,成员变量,构造方法,内部类,静态变量等
类的实现:实现了Lock和序列化

	//实现了Lock接口 声明了序列化
public class ReentrantLock implements Lock, java.io.Serializable {
    
    
    private static final long serialVersionUID = 7373984872572414699L;
    
    //重写了Lock方法
    public void lock() {
    
    
        sync.lock();
    }
	..................................
    }

因为实现了Lock接口,所以重写其接口方法,只是方法具体内部实现都委托给了sync

内部基类Sync ,内部类FairSync和NonfairSync

	//提供所有实现机制的同步器
    private final Sync sync;

代码看到 类Sync(同步器)的非访问修饰符是final,意味着 sync 对象一旦被创建并赋值,就不能再指向另一个对象。而访问修饰符是private 意味这Sysn只能在本类ReentrantLock 中使用

再来看内部类Sync的定义

    // 抽象类继承AbstractQueuedSynchronizer (AQS)
    abstract static class Sync extends AbstractQueuedSynchronizer {
    
    
        private static final long serialVersionUID = -5179523762034025860L;
        ......内部类方法体...............
	}

static修饰:可独立于外部类实例存在,不依靠外部类引用,减少内存泄漏风险;
abstract修饰:抽象类不能被实例化,但可以作为基类让子类继承,实现特定的同步逻辑支持多态扩展
同时继承了AbstractQueuedSynchronizer(AQS),说明是依靠AQS大体上实现同步逻辑。

再来看具体继承基类Sync 实现实例化的两个子内部类

	static final class NonfairSync extends Sync {
    
    
	......NonfairSync 方法体...............
	}
    static final class FairSync extends Sync {
    
    
    ......FairSync方法体...............
    }

继承基类Sync的类是另外两个内部类FairSync 和NonfairSync ,修饰符static final,静态常量化内部类,意思就是随着类加载就存在的两个类,且一旦创建就不在改变,提高安全度

构造方法

public class ReentrantLock implements Lock {
    
    
    private final Sync sync;
    // 构造方法:默认非公平锁,可指定公平策略
    public ReentrantLock(boolean fair) {
    
    
        sync = fair ? new FairSync() : new NonfairSync();
    }
    // 核心方法委托给sync
    public void lock() {
    
     sync.lock(); }
    public void unlock() {
    
     sync.release(1); }
    // 其他方法(如tryLock)略
}

两个构造器 默认是非公平锁,说明ReentrantLock 有两种模式非公平锁和公平锁,由于构造方法返回的是Sync说明ReentrantLock 实际实现是在Sync里面,而重写的Lock方法返回也是sync.xxx这样,下面我就开始解读Sync具体实现功能

基类Sync(同步器)实现的功能方法

继承AQS,又是静态 抽象的修饰符,上面已经介绍了这些作用,这里不复述,开始介绍方法体

abstract static class Sync extends AbstractQueuedSynchronizer {
    
    
        private static final long serialVersionUID = -5179523762034025860L;
	}

1. lock() 方法
抽象类里的抽象方法,需要子类FairSync,NonfairSync 继承并且去实现具体逻辑

// 抽象方法:子类需实现锁的获取逻辑
     abstract void lock();

2.nonfairTryAcquire(int acquires)

        final boolean nonfairTryAcquire(int acquires) {
    
    
            final Thread current = Thread.currentThread();
            //获取同步状态(重入锁值)
            int c = getState();
            //判断锁未被占用(state == 0)
            if (c == 0) {
    
    
            	// 直接尝试获取锁,不检查队列
                if (compareAndSetState(0, acquires)) {
    
    
                	//设置当前拥有独占访问权限的线程
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //判断 锁已被当前线程占用(重入:增加重入次数)
            else if (current == getExclusiveOwnerThread()) {
    
    
            	// 重入逻辑:增加重入次数 理论上一般acquires通常是1
                int nextc = c + acquires;
                if (nextc < 0) // 若重入次数溢出(nextc < 0)抛出错误
                    throw new Error("Maximum lock count exceeded");
                //否则更新state并返回true
            //无需同步:由于只有持有锁的线程能执行此逻辑,直接使用setState而非CAS,确保线程安全
                setState(nextc);
                return true;
            }
            //若锁被其他线程占用,或CAS竞争失败,返回false,触发线程加入等待队列
            return false;
        }

总结:方法被final修饰 代表该方法不能被重写,非公平性体现:不管队列先不排队,新线程可直接插队尝试获取锁。
逻辑解析:

①判断锁未被占用(state == 0)
Ⅰ.直接尝试CAS获取锁:通过compareAndSetState(0, acquires)尝试将锁状态从0修改为1(acquires通常为1)。
Ⅱ.若成功,则设置当前线程为锁的独占所有者,返回true。

②判断锁已被当前线程占用(重入):
Ⅰ.若当前线程已是锁的所有者,增加重入次数:将state增加acquires(通常是1)。若重入次数溢出(nextc < 0),抛出错误;否则更新state并返回true。
Ⅱ.无需同步:由于只有持有锁的线程能执行此逻辑,直接使用setState而非CAS,确保线程安全。

③获取失败:
若锁被其他线程占用,或CAS竞争失败,返回false,触发线程加入等待队列。

3.tryRelease()

        protected final boolean tryRelease(int releases) {
    
    
        	//通过 int c = getState() - releases 计算释放后的状态值
            int c = getState() - releases;
            //线程合法性检查:检查当前线程是否是持有独占锁的线程:
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            //若 c == 0,表示锁完全释放,将独占线程置为 null,并标记 free = true。
            //否则若 c > 0,表示仍处于重入状态,锁未完全释放,free 保持 false。
            if (c == 0) {
    
    
                free = true;
                setExclusiveOwnerThread(null);
            }
            //无论是否完全释放,都通过 setState(c) 更新同步状态。这确保了重入场景下状态逐步递减
            setState(c);
            //返回 free,告知上层(如 release() 方法)是否需要唤醒后续线程。
            return free;
        }

这又是一个final修饰的方法 代表不能被重写,同时这个方法有向上引用,是AQS里面方法的重写实现,这是AQS里面钩子方法tryRelease的其中一个实现

总结:
①通过计算释放锁的值释放归零 判断锁是否完全释放,所以重入锁多少次 就需要释放锁多少次,是一种原子性清零,当然也可以一次性释放,比如重入锁是3次直接传参释放3次,ConditionObject里面就是这么做的。无论是否完全释放都通过 setState ( c ) 更新同步状态

②里面有线程合法性检查,由于只有持有锁的线程能调用此方法,无需额外同步。所以这个方法是线程安全的

4.isHeldExclusively()
是一个用于判断当前线程是否独占持有锁的方法

	//通过 getExclusiveOwnerThread() 获取当前持有锁的线程,并与当前线程比较
      protected final boolean isHeldExclusively() {
    
    
            return getExclusiveOwnerThread() == Thread.currentThread();
        }
	//getExclusiveOwnerThread方法
    protected final Thread getExclusiveOwnerThread() {
    
    
        return exclusiveOwnerThread;
    }
    //exclusiveOwnerThread变量值
    private transient Thread exclusiveOwnerThread;

这又是一个final修饰的方法 代表不能被重写,同时这个方法有向上引用,是AQS里面方法的重写实现,这是AQS里面钩子方法isHeldExclusively的其中一个实现

检查独占性:方法实现内容:确认当前线程是否是锁的独占持有者(即是否持有锁)。
支持条件变量:在 Condition 的实现中(如 await() 和 signal()),需要确保调用线程持有锁,此时会调用此方法进行验证

5.newCondition()
类中本地方法 没重写其余类或接口方法,有final说明不可以被重写,

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

1.return new ConditionObject()返回ConditionObject 实例,创建条件变量 支持线程的精确等待唤醒
2.每个ConditionObject 必须与一个独占锁(如 ReentrantLock)配合使用,只有持有锁的线程才能调用其 await()、signal() 等方法
3.可以多次调用 newCondition() 创建多个独立的 Condition 对象

6.getOwner()
getOwner() 方法的作用是获取当前持有独占锁的线程(即锁的拥有者)

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

通过 state 和 exclusiveOwnerThread 的双重判断,确保方法的返回值符合锁的实际状态:
若 state(同步状态)为 0,表示锁未被任何线程持有,返回 null。
当 state > 0 时,锁已被持有,getExclusiveOwnerThread必然指向有效线程。返回当前独占线程exclusiveOwnerThread

7.getHoldCount()
getHoldCount() 方法的作用是获取当前线程对锁的重入次数(即锁被同一线程重复获取的次数)

        final int getHoldCount() {
    
    
            return isHeldExclusively() ? getState() : 0;
        }

若当前线程独占持有锁(通过 isHeldExclusively() 判断),返回 state 的值,表示锁的重入次数 + 1(例如 state=3 表示线程重入了 2 次)。
若当前线程未持有锁,直接返回 0

8.isLocked()
isLocked() 方法的作用是快速判断锁是否被占用(无论持有者是谁)

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

锁占用状态检查
若 state(同步状态)不等于 0,表示锁被某线程持有(可能是当前线程或其他线程),返回 true。
若 state 等于 0,表示锁未被任何线程持有,返回 false
private volatile int state;仅通过读取 volatile 变量 state 完成,无锁竞争,性能高效。

与 getOwner() 的差异,isLocked():仅检查锁是否被占用(state != 0),而getOwner():返回持有锁的具体线程(需结合 state 判断)。

9.readObject
在反序列化时重置锁状态,确保反序列化后的对象处于未锁定状态

        private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException {
    
    
            s.defaultReadObject();// 默认反序列化流程
            setState(0);  // 强制将锁状态重置为未锁定
        }

反序列化安全性
锁的持有状态(state 和 exclusiveOwnerThread)与运行时线程上下文紧密相关。序列化时可能捕获到一个被锁定的状态,但反序列化时原持有线程已不存在,导致锁永久不可用。通过强制重置状态setState(0) ,确保反序列化后的锁处于未锁定的初始状态。

内部类 非公平锁NonfairSync

该内部类继承了抽象类Sync 同时又作为ReentrantLock构造方法的默认返回类型,代码如下

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

        // 重写了sync的lock方法
        final void lock() {
    
    
        	//使用CAS操作将state从0改为1。如果成功,说明当前线程获取了锁
            if (compareAndSetState(0, 1))
            	//设置当前线程为独占拥有者
                setExclusiveOwnerThread(Thread.currentThread());
            else
            	//这是AQS的核心方法,用于处理锁获取失败后的排队和重试逻辑
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
    
    
            return nonfairTryAcquire(acquires);
        }
    }

非公平锁有两个方法,先看tryAcquire方法,该方法是AQS里面的钩子方法tryAcquire重写的,其返回值代表尝试获取锁成功或失败,具体实现是在Sync类中nonfairTryAcquire方法,该方法已经在上面阐述了 这里不在复述

lock方法逻辑步骤
①使用CAS操作将state从0改为1。如果成功,说明当前线程获取了锁
②如果CAS操作成功设置当前线程为独占拥有者
③如果CAS操作失败,那就再去acquire方法试试,acquire是AQS核心方法之一,有意思的是acquire方法也是先尝试获取锁,获取失败了(tryAcquire(arg)==false)才加入同步队列(addWaiter),然后不断重试获取锁(acquireQueued)

    public final void acquire(int arg) {
    
    
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

内部类 公平锁FairSync

公平锁:该内部类继承了抽象类Sync ,是ReentrantLock构造方法的返回类型之一

    static final class FairSync extends Sync {
    
    
        private static final long serialVersionUID = -3000897897090466540L;
		
        final void lock() {
    
    
            acquire(1);
        }
		//参数:acquires 表示请求的锁数量(通常为1)。
        protected final boolean tryAcquire(int acquires) {
    
    
        	//步骤1:获取当前线程与锁状态c
            final Thread current = Thread.currentThread();
            int c = getState();
            //处理锁未被持有的情况(c == 0)
            if (c == 0) {
    
    
            	// 公平性检查:是否有更早的等待线程?
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
    
    
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //判断确认当前线程已经是锁的持有者,允许重入
            else if (current == getExclusiveOwnerThread()) {
    
    
            	//更新重入次数:计算新的重入次数(如原 state=2,acquires=1,则 nextc=3)
                int nextc = c + acquires;
                //溢出检查:若 nextc < 0(如 state=Integer.MAX_VALUE 时加1导致溢出)
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                 //更新状态:setState(nextc) 直接设置新值(无需CAS,因为只有持有线程能修改)。
                setState(nextc);
                return true;
            }
            return false;
        }
    }

老规矩先看tryAcquire方法,这里tryAcquire方法自行实现了功能逻辑
步骤1:先获取当前线程和同步状态
步骤2:同步状态==0判断是否当前锁未被线程持有,那就做公平性检查:是否有更早的等待线程
条件1:!hasQueuedPredecessors():检查同步队列中是否有比当前线程更早等待的线程,是AQS的方法通过检查头尾节点判断队列状态
条件2:compareAndSetState(0, acquires):CAS操作将 state 从 0 更新为 acquires(通常为1),独占锁模式下 同一时刻只有一个线程能成功修改状态
如果条件1和2都满足 说明没有更早的等待线程,获取锁也能行,那就设置当前线程为独占持有者 直接返回ture表示锁获取成功
步骤3:如果锁已被当前线程持有 那这次获取锁相当于重入一次,处理下重入更新下同步状态

公平锁的公平体现在锁未被线程持有情况下,先判断是否有更早的等待线程

再来看重写的lock方法 比较简单,其核心依然是AQS的acquire方法,但区别于非公平锁的是,acquire方法里面的tryAcquire方法调用的公平锁的tryAcquire方法,构造器用的哪个内部类锁类型,对应的AQS方法最终调用的是哪个内部类锁类型的方法