阅读 206

Java并发学习笔记:ReentrantLock

前言

  ReentrantLock是可重入的独占锁,位于JUC包里,是一种非常常用的锁机制。最近我也正好在学习ReetrantLock,感觉这块还比较复杂,值得记录一下。本文主要记录,解释 ReentrantLock 锁的获取,释放等过程。

锁的结构

public class ReentrantLock implements Lock, java.io.Serializable {
    private static final long serialVersionUID = 7373984872572414699L;
    /** Synchronizer providing all implementation mechanics */
    private final Sync sync;  
    // ...
    public ReentrantLock() {
        sync = new NonfairSync();
    }
    // ...
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
    // ...
    public void lock() {
        sync.lock();
    }
    // ...
    public void lockInterruptibly() throws InterruptedException {
        sync.lockInterruptibly();
    }
    // 其它函数
复制代码

  查看ReentrantLock源码可知,其内部有一个Sync类型的变量sync,上述代码列出了一些常见操作,均是调用sync里的对应方法。可见,ReetrantLock主要是通过sync这个变量来实现这些常见方法。

abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = -5179523762034025860L;
复制代码

  接着查看Sync这个类,发现是ReetrantLock的内部类,继承了大名鼎鼎的AbstractQueuedSynchronizer(即AQS)类。跟锁有关的主要操作都是在这个类中实现。下面看一些具体的操作。

锁对象的创建

  使用ReentrantLock,首先得用ReentrantLock lock = new ReentrantLock();语句来创建一个锁的对象。看一下源码里ReentrantLock的构造函数。

public ReentrantLock() {
    sync = new NonfairSync();
}
// other code
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}
复制代码

  可见,构造函数创建了Sync的对象sync(NonfairSync和FairSync都是Sync的子类,分别代表非公平锁和公平锁)。可以看出,默认的无参情况下,创建的是非公平锁;带参情况下,fair传true时,会创建公平锁。

获取锁

public void lock() {
    sync.lock();
}  
public boolean tryLock() {
    return sync.tryLock();
}
复制代码

  锁的获取主要是这两个函数,当然还有 lockInterruptibly( ) 和 tryLock(long timeout, TimeUnit unit) 这种响应中断和带时间限制的函数,不过和普通的lock( )和tryLock( )机理大致相同,就不介绍了。先以非公平锁的lock流程为例:

// 位于Sync中
final void lock() {
    if (!initialTryLock())
        acquire(1);
}  
// 位于NonfairSync中
final boolean initialTryLock() {
    Thread current = Thread.currentThread();
    if (compareAndSetState(0, 1)) { // first attempt is unguarded
        setExclusiveOwnerThread(current);
        return true;
    } else if (getExclusiveOwnerThread() == current) {
        int c = getState() + 1;
        if (c < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(c);
        return true;
    } else
        return false;
}  
// 位于AQS中
public final void acquire(int arg) {
    if (!tryAcquire(arg))
        acquire(null, arg, false, false, false, 0L);
}  
// 位于NonfairSync中的 
protected final boolean tryAcquire(int acquires) {
    if (getState() == 0 && compareAndSetState(0, acquires)) {
        setExclusiveOwnerThread(Thread.currentThread());
        return true;
    }
    return false;
}
复制代码

  可见,一个完整的最长的调用关系为:sync.lock( )->sync.initialTryLock( )->acquire(1)->tryAcquire(1)->acquire(many args)。
详细流程为:
先调用initialTryLock( ),方法首先尝试用CAS方式将state从0设置为1(state是AQS类的一个变量,用来说明锁是否被获取,自然被Sync类继承了),成功了就将此锁的拥有线程设置为此线程;否则,查看此线程是否已经拥有此锁,若是,则设置state的重入次数,由此可见,ReentrantLock是可重入锁,一个线程可多次获取。否则,返回false。

接着调用AQS类的acquire( ),首先会调用tryAcquire( )函数,这个函数是由NonfairSync重写的。在这里会再检查一下此锁是否被释放,若是,直接获取它,否则,返回false。

如果以上尝试都返回false了,说明这个锁一时半会确实获取不到,就调用AQS类的带许多参数的acquire( )函数,这个函数的作用是把这个线程放入这个锁的阻塞队列里。是AQS的内容,这里就不介绍了。

上面介绍了非公平锁的获取锁的流程。公平锁和非公平锁各自实现了initiTryLock( )和tryAcquire( )方法。对于公平锁来说,相较于非公平锁,获取锁的其它代码全都一样,只是在设置一个线程获取锁时,会多一个!hasQueuedThreads()判断,这是AQS里的方法,作用是判断有没有线程在此线程前面被阻塞。这就体现了所谓的公平和非公平:非公平锁不检查有没有线程在它前面,只要发现锁可以获取就直接获取,所以不公平(你先等的,结果它时机凑巧,来的晚还把锁抢走了),公平锁就比较‘公平’了。

再看tryLock():

final boolean tryLock() {
    Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, 1)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    } else if (getExclusiveOwnerThread() == current) {
        if (++c < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(c);
        return true;
    }
    return false;
}
复制代码

  这个方法在Sync类里实现,所以不管是不是公平锁,可以获取就直接获取锁。代码和initialTyeLock( )类似,就不解释了。同时也可以看出,tryLock( )并不会把线程加入到阻塞队列里,获取失败就直接返回false了。

锁的释放

位于Sync中
public void unlock() {
    sync.release(1);
}  
// 位于AQS中
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        signalNext(head);
        return true;
    }
    return false;
}  
// 位于Sync中
protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (getExclusiveOwnerThread() != Thread.currentThread())
        throw new IllegalMonitorStateException();
    boolean free = (c == 0);
    if (free)
        setExclusiveOwnerThread(null);
    setState(c);
    return free;
}
复制代码

  释放锁时,首先调用AQS中的release(1)函数,接着调用Sync重写的tryRelease( )函数,在这里将锁的重入数减一,如果state变为了0,说明此锁被释放了,进行释放锁的操作setExclusiveOwnerThread(null);。如果锁被释放了,会接着调用AQS的signalNext( )函数功能。通知其它线程此锁可获得。
值得一提的是,这里的tryRelease( )函数和上述的tryAcquire( )函数不一样,后者由公平锁和非公平锁各自实现,而tryRelease( )则在Sync类里实现,公平锁和非公平锁共用。这是因为释放锁不用考虑是否公平,直接释放了就可以了。

创建条件对象

ReentrantLock另一个常用方法就是创建条件对象,在源码里也很简单。

public Condition newCondition() {
    return sync.newCondition();
}  
// 位于AQS
final ConditionObject newCondition() {
    return new ConditionObject();
}
复制代码

   newCondition( )方法和ConditionObject的主要操作都定义在AQS类里,这里不做介绍了。

image.png

  一个ReentrantLock对象对应的阻塞和条件队列示意图如上。相关的AQS操作之后有空我会写一下。

锁的状态state

  在上面获取和释放锁的代码里,经常会出现getState( )这个方法的身影。这个方法实际上是返回了state变量。state是AQS类的一个变量,在ReentrantLock里,用来标识锁的重入次数以及是否被持有。下面是关于它的一些方法:

private volatile int state;

protected final int getState() {
    return state;
}

protected final void setState(int newState) {
    state = newState;
}

protected final boolean compareAndSetState(int expect, int update) {
    return U.compareAndSetInt(this, STATE, expect, update);
}
复制代码

  这些方法均位于AQS类里。首先,state是一个volatile类型的变量,保证每个线程读到的state值都是最近一个线程更新的值。get和set方法比较简单,不再解释。最后有一个compareAndSetState( )方法:顾名思义,用CAS方式更新state的值。这里使用Unsafe类(类似于一个指针,直接操作对应地址的数据)进行更新:

private static final Unsafe U = Unsafe.getUnsafe();
// 获取属性偏移量
private static final long STATE
    = U.objectFieldOffset(AbstractQueuedSynchronizer.class, "state");
复制代码

锁的持有线程

  在上面的锁的获取和释放里提到了 setExclusiveOwnerThread( )和getExclusiveOwnerThread( )两个方法。接下来介绍下锁的持有线程的相关内容。

// 位于AbstractOwnableSynchronizer类中
protected final void setExclusiveOwnerThread(Thread thread) {
    exclusiveOwnerThread = thread;
}

protected final Thread getExclusiveOwnerThread() {
    return exclusiveOwnerThread;
}
复制代码

  上面提到了Sync类继承了AQS类,而AQS类继承了AbstractOwnableSynchronizer类,在AbstractOwnableSynchronizer类中有一个属性private transient Thread exclusiveOwnerThread; ,用来标识锁被哪个线程持有。所以上面的set和get方法实际上是对这个属性进行操作。

protected final boolean isHeldExclusively() {
    return getExclusiveOwnerThread() == Thread.currentThread();
}    
// 其它代码
final Thread getOwner() {
    return getState() == 0 ? null : getExclusiveOwnerThread();
}
复制代码

基于这个属性,ReentrantLock内有一些其它方法比如上面两个,都比较简单。

总结

  本文对ReentrantLock的常用方法进行了源码级别的探究和记录。不过涉及到AQS的操作都直接略去了,之后作者还会研究下AQS类。

文章分类
后端
文章标签