• 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏吧

ReentrantLock源码分析

开发技术 开发技术 2周前 (09-06) 26次浏览

ReentrantLock源码分析

ReentrantLock是独享锁,同时是基于AQS实现的,因此它内部肯定是通过自定义AQS独占模式下的同步器来实现独享锁,该同步器需要重写AQS提供的tryAcquire()和tryRelease()方法,只需要告诉AQS尝试获取同步资源和释放同步资源是否成功即可。

AQS子类需要定义以及维护同步状态的值,在ReentrantLock中使用state为0表示同步资源没有被线程所持有,使用state不为0表示同步资源已经被线程所持有。

ReentrantLock有公平和非公平两种模式,公平模式是指多线程按照申请锁的顺序来获取锁,非公平模式是指多线程并非按照申请锁的顺序来获取锁。

ReentrantLock源码分析

ReentrantLock的结构

public class ReentrantLock implements Lock, java.io.Serializable {
    
    private final Sync sync;

    /**
     * 抽象同步器(AQS独占模式)
     */
    abstract static class Sync extends AbstractQueuedSynchronizer {
       // ......
    }
    
    /**
     * 非公平同步器
     */
    static final class NonfairSync extends Sync {
       // ......
    }

    /**
     * 公平同步器
     */
    static final class FairSync extends Sync {
      // ......
    }

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

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

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

    public boolean tryLock() {
        return sync.nonfairTryAcquire(1);
    }

    public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }

    public void unlock() {
        sync.release(1);
    }
  
    // 其它省略
}

可以发现ReentrantLock中定义了一个抽象同步器(Sync)、非公平同步器(NonfairSync)、公平同步器(FairSync),同时非公平同步器和公平同步器都继承抽象同步器。

同时ReentrantLock中存在一个全局的抽象同步器属性(sync),通过ReentrantLock的构建方法来进行初始化,并通过参数来指定是使用公平同步器还是非公平同步器,默认情况下是使用非公平同步器。

同时ReentrantLock中的核心方法,如lock()加锁方法,是调用抽象同步器声明的lock()方法,unlock()解锁方法,是调用AQS的release()方法,而tryLock()方法,是调用抽象同步器提供的nonfairTryAcquire()方法。

剖析抽象同步器

/**
 * 抽象同步器(AQS独占模式)
 */ 
abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = -5179523762034025860L;

    /**
     * 声明了lock()方法,用于加锁
     * ReentrantLock的lock()方法会调用抽象同步器声明的lock()方法
     */ 
    abstract void lock();

    /**
     * 用于尝试获取锁,如果获取成功则返回true,并修改同步状态的值,否则返回false
     * ReentrantLock的tryLock()方法会调用抽象同步器提供的nonfairTryAcquire()方法
     */
    final boolean nonfairTryAcquire(int acquires) {
        // 获取当前线程
        final Thread current = Thread.currentThread();
        // 获取同步状态
        int c = getState();
        // 如果同步状态为0,表示锁没有被线程所持有
        if (c == 0) {
            // 通过CAS将同步状态设置为1,获取锁
            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;
    }

    /**
     * 重写AQS的tryRelease()方法,用于尝试释放锁,如果释放成功,则返回true,并修改同步状态的值,否则返回false
     */ 
    protected final boolean tryRelease(int releases) {
  		// 获取锁释放后剩余的同步资源
        int c = getState() - releases;
        // 如果拥有锁的线程并非当前线程,则直接抛出异常(要求谁加的锁只能由谁进行解锁)
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        
        boolean free = false;
        // 如果锁释放后,state的值为0,则返回true,否则返回false(说明线程加了不止一把锁,然后又没有全释放)
        if (c == 0) {
            free = true;
            // 清空拥有同步资源的线程
            setExclusiveOwnerThread(null);
        }
        setState(c);
        return free;
    }

    /**
     * 判断当前线程是否持有锁
     */
    protected final boolean isHeldExclusively() {
        return getExclusiveOwnerThread() == Thread.currentThread();
    }

    /**
     * 返回Condition实例,在某些场景下可以进行阻塞与唤醒
     */
    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;
    }

}

抽象同步器声明的lock()方法用于加锁,该方法需要由抽象同步器的子类,也就是非公平同步器和公平同步器来实现。

同时抽象同步器中提供了nonfairTryAcquire()方法,该方法会在ReentrantLock的tryLock()方法以及非公平同步器的tryAcquire()方法中被调用。

tryAcquire()方法由抽象同步器的子类来重写,而tryRelease()方法由抽象同步器来重写。

剖析非公平同步器

/**
 * 非公平同步器(继承抽象同步器)
 */
static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;

    /**
     * 实现抽象同步器声明的lock()方法
     */
    final void lock() {
        // 直接通过CAS尝试获取锁,如果获取成功则将当前线程设置为拥有同步资源的线程,然后直接返回,否则调用AQS的acquire()方法
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }

    /**
     * 重写AQS的tryAcquire()方法,直接调用抽象同步器的nonfairTryAcquire()方法
     */
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

非公平模式下的ReentrantLock的加锁,将会调用非公平同步器的lock()方法,该方法先通过CAS尝试获取锁,当获取失败时才会调用AQS的acquire()方法,该方法又会调用非公平同步器的tryAcquire()方法。

非公平模式下的ReentrantLock的解锁,将会调用AQS的release()方法,该方法又会调用抽象同步器提供的tryRelease()方法。

剖析公平同步器

/**
 * 公平的同步器(继承抽象同步器)
 */
static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;

    /**
     * 实现抽象同步器声明的lock()方法
     */
    final void lock() {
        // 直接调用AQS的acquire()方法
        acquire(1);
    }
    
	/**
     * 重写AQS的tryAcquire()方法
     */
    protected final boolean tryAcquire(int acquires) {
        // 获取当前线程
        final Thread current = Thread.currentThread();
        // 获取同步状态
        int c = getState();
        // 如果同步状态为0,同时等待队列中头节点的后继节点封装的线程是当前线程,那么才会通过CAS将同步状态设置为1,表示获取锁(这个判断就是用于保证公平锁的)
        if (c == 0) {
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current); // 将拥有同步资源的线程设置为当前线程
                return true;
            }
        }
        // 如果同步状态不为0,也就是锁已经被线程所持有,同时如果当前线程就是拥有锁的线程,它还尝试获取锁,那么就累加同步状态的值,然后返回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;
    }
}

公平模式下的ReentrantLock的加锁,将会调用公平同步器的lock()方法,该方法直接调用AQS的acquire()方法,该方法内部又调用公平同步器的tryAcquire()方法。

公平模式下的ReentrantLock的解锁,将会调用AQS的release()方法,该方法又会调用抽象同步器提供的tryRelease()方法。

非公平模式下的总结

1.当线程要获取锁时,可以直接调用lock()和tryLock()方法。

2.如果调用lock()方法,那么将会调用非公平同步器的lock()方法,该方法会先通过CAS尝试获取锁,如果获取锁成功则直接返回,否则将会调用AQS的acquire()方法。

3.AQS的acquire()方法又会调用非公平同步器的tryAcquire()方法,该方法直接调用抽象同步器的nonfairTryAcquire()方法,如果同步状态的值为0,则通过CAS尝试获取锁,如果获取锁成功则返回true,否则返回false,同时如果同步状态的值不为0,同时当前线程就是拥有锁的线程,那么允许它继续进行加锁,然后累加同步状态的值,这种情况也会返回true。

4.如果调用tryLock()方法,那么将会调用抽象同步器的nonfairTryAcquire()方法。

5.当线程要释放锁时,将会调用ReentrantLock的unlock()方法,该方法直接调用AQS的release()方法,该方法又会调用抽象同步器的tryRelease()方法,如果线程释放锁后,同步状态的值为0,则返回true,否则说明线程加了不止一把锁,那么会更新同步状态的值,然后返回false,只有当线程把它加的锁都释放后,tryRelease()方法才会返回true。

公平模式下的总结

1.当线程要获取锁时,可以直接调用lock()和tryLock()方法。

2.如果调用lock()方法,那么将会调用公平同步器的lock()方法,该方法直接调用AQS的acquire()方法。

3.AQS的acquire()方法又会调用公平同步器的tryAcquire()方法,该方法中只有当同步状态的值为0,同时等待队列中的头节点的后继节点封装的线程是当前线程时,才会通过CAS尝试获取锁,如果获取锁成功则返回true,否则返回false,同时如果同步状态的值不为0,同时当前线程就是拥有锁的线程,那么允许它继续进行加锁,然后累加同步状态的值,这种情况也会返回true。

4.如果调用tryLock()方法,那么将会调用抽象同步器的nonfairTryAcquire()方法。

5.当线程要释放锁时,将会调用ReentrantLock的unlock()方法,该方法直接调用AQS的release()方法,该方法又会调用抽象同步器的tryRelease()方法,如果线程释放锁后,同步状态的值为0,则返回true,否则说明线程加了不止一把锁,那么会更新同步状态的值,然后返回false,只有当线程把它加的锁都释放后,tryRelease()方法才会返回true。

FAQ

关于ReentrantLock的tryLock()方法

ReentrantLock的tryLock()方法是非公平的,因为无论在什么模式下,ReentrantLock的tryLock()方法总是调用抽象同步器的nonfairTryAcquire()方法,因此当线程释放锁时,需要唤醒离头节点最近的同时等待状态不为CANCELLED的后继节点,然后在该节点尝试获取锁之前,其他线程直接调用了tryLock()方法获取了锁,那么被唤醒的这个线程又只能再进入阻塞状态,这就是非公平的体现。

关于获取了锁的线程能否再进行加锁?

是可以的,因为无论在非公平模式下还是公平模式下,tryAcquire()方法当中都会有这么一个判断,也就是如果当前同步状态的值不为0,表示锁已经被线程所持有,同时当前线程就是拥有锁的线程,那么允许它继续进行加锁,然后累加同步状态的值,这种情况方法也会返回true,同时在抽象同步器重写的tryRelease()方法,如果线程释放锁后,同步状态的值为0,则返回true,否则说明线程加了不止一把锁,那么会更新同步状态的值,然后返回false,只有当线程把它加的锁都释放后,tryRelease()方法才会返回true。

非公平锁是如何实现非公平的?

主要体现在非公平同步器的lock()方法,当线程要进行加锁时,并没有直接调用AQS的acquire()方法,而是先通过CAS尝试获取锁,因此当线程释放锁时,需要唤醒离头节点最近的同时等待状态不为CANCELLED的后继节点,然后在该节点尝试获取锁之前,其他线程直接调用了lock()方法获取了锁,那么被唤醒的这个线程又只能再进入阻塞状态,这就是非公平的体现。

公平锁是如何实现公平的?

主要体现在公平同步器的lock()和tryAcquire()方法,首先lock()方法直接调用AQS的acquire()方法,并没有像非公平同步器的lock()方法一样,先通过CAS尝试获取锁,然后在tryAcquire()方法中,只有当同步状态的值为0,同时等待队列中的头节点的后继节点封装的线程是当前线程时,才会通过CAS尝试获取锁,因此当线程释放锁时,需要唤醒离头节点最近的同时等待状态不为CANCELLED的后继节点,然后在该节点尝试获取锁之前,其他线程调用了lock()方法进行加锁,由于lock()方法直接调用AQS的acquire()方法,然后acquire()方法又调用公平同步器的tryAcquire()方法,虽然判断到当前同步状态的值为0,但是当前线程并不是等待队列中头节点的后继节点封装的线程,因此该线程也只能封装成Node节点,然后加入到等待队列当中。


程序员灯塔 , 版权所有
转载请注明原文链接:https://www.wangt.cc/2020/09/reentrantlock%e6%ba%90%e7%a0%81%e5%88%86%e6%9e%90/
喜欢 (1)