多线程基础(十五):ReentrantLock源码分析

2020-11-03  本文已影响0人  冬天里的懒喵

[toc]

1.类结构及注释

1.1 类结构

ReentrentLock是java并发包中的一个重要的同步工具,其设计本身与synhronized的作用类似,实现了一个互斥锁。其产生的原因是在java的早期,synchronized的效率比较低,其阻塞大量使用了系统调用中的重量级锁0x80,这样导致需要从内核态和用户态切换,从而影响系统的效率。因此,为了系统效率而基于Unsafe的并发同步框架AbstracctQueueSynchronizer而设计了ReentrentLock。这个类结构如下图:


image.png

可以看到,ReentrentLock实现了Lock和Serializable接口,其内部有三个类,继承了抽象类AbstractQueueSynchronizer的Sync类,以及其实现公平和非公平锁的子类FairSync及NonfairSync。

1.2 类注释

ReentrentLock类前面的注释如下:
ReentrantLock是一个可重入的互斥锁,其基本行为和语义与使用Synchronized方法和语句的隐式管程锁相同,但是具有更多的扩展功能。
ReentrantLock由上次成功锁定但是尚未解锁的线程拥有,当该锁不属于另一个线程的时候,调用lock的线程将返回,并成功获得该锁。如果当前线程已拥有该锁,则该方法将立即返回。可以使用isHeldByCurrentThread和getHoldCount进行检查。
此类的构造函数接收一个可选的公平参数,在争用的情况下,锁倾向于授予对等待时间最长的线程访问,否则,此锁不能保证按任何特定方式的顺序。使用许多线程访问的公平锁定的程序可能会比默认设置的程序现实较慢的吞吐量(通常要慢得多)但获得锁并保证没有饥饿的时间差异较小,但是请注意,锁的公平性不能保证线程调度的公平性,因此,使用公平锁的许多线程之一可能连续多次获得它,而其他活动线程未进行且当前未持有锁,还要注意,未定时的tryLock方法不支持公平性设置,如果锁定可用,如果其他线程正在等待,它任然将成功。
建议实践总是在使用try之后立即lock,最常用的是在构造前后,如:

  class X {
    private final ReentrantLock lock = new ReentrantLock();
    // ...
 
    public void m() {
      lock.lock();  // block until condition holds
      try {
        // ... method body
      } finally {
        lock.unlock()
      }
    }
  }}

除了实现lock接口之外,此类还定义了许多public和protected的方法来检查锁的状态。其中一些方法仅仅用于监控。
此类的序列化和内置锁的行为相同,反序列化的锁处于解锁状态,而不管序列化时锁的状态如何。
此锁同一线程最多支持2147483647个递归锁,尝试超过此限制会导致方法被锁定且抛出Error异常。

2.构造函数及成员变量

ReentrantLock的成员变量实际上就一个:

 private final Sync sync;

其构造函数都是围绕这个成员变量来实现公平与非公平锁。

2.1 ReentrantLock()

/**
 * Creates an instance of {@code ReentrantLock}.
 * This is equivalent to using {@code ReentrantLock(false)}.
 */
public ReentrantLock() {
    sync = new NonfairSync();
}

默认情况下,sync的值为非公平锁NonfairSync。

2.2 ReentrantLock(boolean fair)

/**
 * Creates an instance of {@code ReentrantLock} with the
 * given fairness policy.
 *
 * @param fair {@code true} if this lock should use a fair ordering policy
 */
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

这个构造函数可以根据fair的参数来返回,如果为true则返回公平锁FairSync,反之则返回非公平锁NonfairSync。

3.公平锁与非公平锁的内部类

ReentrentLock是采用内部类Sync的子类来分别实现公平锁和非公平锁的。Sync是AbstractQueueSynchronizer的子类。实现了前面讨论AQS中的一些模板方法。


image.png

3.1 Sync

Sync是一个抽象类,其定义了一个抽象方法lock,其子类需要实现这个方法。

/**
 * Base of synchronization control for this lock. Subclassed
 * into fair and nonfair versions below. Uses AQS state to
 * represent the number of holds on the lock.
 */
abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = -5179523762034025860L;

    /**
     * Performs {@link Lock#lock}. The main reason for subclassing
     * is to allow fast path for nonfair version.
     */
    abstract void lock();
}

我们来看看Sync的主要方法:

3.1.1 nonfairTryAcquire

这是非公平锁的tryAcquire方法。

final boolean nonfairTryAcquire(int acquires) {
    //获取当前线程
    final Thread current = Thread.currentThread();
    //获取当前的state 到变量c
    int c = getState();
    //如果c为0 则说明当前的锁空闲
    if (c == 0) {
        //由于是非公平锁,只要判断c为0 ,则上来先抢锁,不管等待队列中是否有数据。
        //采用cas的方式set State,这个方法确保在并发的情况下只有一个线程能成功。
        if (compareAndSetState(0, acquires)) {
            //如果成功,则将锁的owner设置为当前线程
            setExclusiveOwnerThread(current);
            //直接返回为true
            return true;
        }
    }
    //如果不为0,且当前线程为此锁的Owner则说明是再次重入
    else if (current == getExclusiveOwnerThread()) {
       //将state的值加上acquires 通常是加1
        int nextc = c + acquires;
        //如果相加之后小于0 则说明字段溢出,此时抛出异常
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        //通过set方法修改状态
        setState(nextc);
        //返回成功
        return true;
    }
    //如果都不是,则返回获取锁失败,外层调用可能需要再次循环
    return false;
}

实际上可以很明显看出,所谓非公平锁,一上来先不排队,而是执行一次竞争,不用管排队的情况。之后如果不成功,则调用tryAcquire的方法将会让这个线程入队。而公平锁的话,直接就会调用AQS自带的排队的方法,根据队列的情况来处理。公平与非公平的区别就在于是否排队。

3.1.2 tryRelease

释放锁的方法。

protected final boolean tryRelease(int releases) {
    //c为当前的state状态减去release 一般是减1
    int c = getState() - releases;
    //如果当前线程不是Owner 则抛出异常
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    //定义free为false
    boolean free = false;
    //如果c为0,则表示state完全释放了锁。
    if (c == 0) {
        free = true;
        //将锁的Owoner状态设置为null
        setExclusiveOwnerThread(null);
    }
    //设置状态为c  
    setState(c);
    //返回free的值
    return free;
}

此处是Sync中对锁的释放方法,无论公平还是非公平锁,这个方法都通用。

3.2 NonfairSync

非公平锁NonfairSync,继承了AbstractQueueSynhronizer其源码如下:

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

    /**
     * Performs lock.  Try immediate barge, backing up to normal
     * acquire on failure.
     */
    final void lock() {
        //非公平锁,所以一开始先竞争一次
        if (compareAndSetState(0, 1))
            //如果成功则设置线程为Owner
            setExclusiveOwnerThread(Thread.currentThread());
        else
            //如果没有竞争成功,则调用acquire进行入队,执行AQS通用操作
            acquire(1);
    }
    
    protected final boolean tryAcquire(int acquires) {
       //tryAcquire调用前面的nonfairTryAcquire方法
        return nonfairTryAcquire(acquires);
    }
}

3.3 公平锁

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

    final void lock() {
    //注意此时与非公平锁的区别,此处直接执行acquire(1),而非公平锁一上来需要先竞争一次
        acquire(1);
    }

    /**
     * Fair version of tryAcquire.  Don't grant access unless
     * recursive call or no waiters or is first.
     */
    protected final boolean tryAcquire(int acquires) {
        //获取当前线程
        final Thread current = Thread.currentThread();
        //获取锁状态
        int c = getState();
        //如果c为0
        if (c == 0) {
            //如果队列中不具有前任节点,且能够cas抢锁成功,则设置owoner并返回true
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        //反之,如果锁的线程为当前线程
        else if (current == getExclusiveOwnerThread()) {
            //判断c相加之后是否越界
            int nextc = c + acquires;
            //为负数则说明越界 抛出异常
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            //set state的状态
            setState(nextc);
            //返回true
            return true;
        }
        //反之返回false
        return false;
    }
}

公平锁与非公平锁的最大的区别就是,公平锁需要根据队列的情况,每次都需要判断是否为队列的head节点,如果不是,则不能抢锁。只有为head节点的时候,才能够竞争资源。而后面的线程只能排队等待。非公平锁则每次循环的时候都会尝试获得锁。

4.其他方法

4.1 lock

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

通过构造函数决定sync传入的是公平还是非公平锁,执行对应实例的lock方法。

4.2 lockInterruptibly

调用lock的Interrupt方法。由于ReentrantLock是独占模式,因此不考虑共享的实现方法。

public void lockInterruptibly() throws InterruptedException {
    sync.acquireInterruptibly(1);
}

4.3 tryLock

这些方法都调用了sync的独占模式的实现方法。

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

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

4.4 unlock

此处调用sync的release方法。

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

4.5 其他工具方法

ReentrantLock还提供了许多工具方法,如下表:

方法 说明
newCondition() new一个条件变量,这个在AQS中将生成一个ConditionObject队列
getHoldCount 查询当前线程持有此锁的次数。,也就是重入的次数
isHeldByCurrentThread 查询当前线程是否持有锁。
isLocked 查询此锁是否被其他线程持有。
isFair 查询锁的实现是否为公平锁。
getOwner 返回当前拥有此锁的线程。
hasQueuedThreads 查询是否有线程正在等待获取此锁。
hasQueuedThread(Thread thread) 查询给定的线程是否正在等待获取这个锁。
getQueueLength 返回等待获取此锁的线程数的估计数。
hasWaiters 查询是否有线程在等待给定的条件变量。
getWaitQueueLength 线程上等待这个条件变量的锁的线程估计数。
getWaitingThreads 返回一个集合,其中包含可能是等待与此锁关联的给定条件。
上一篇下一篇

猜你喜欢

热点阅读