JUC之ReentrantLock

关键词:ReentrantLock    AbstractQueuedSynchronizer    Synchronized

 

在Java中,可以通过使用关键字Synchronized实现锁和线程安全的功能。Synchronized可以看做是JVM级别提供的锁。我所理解的ReentrantLock就是在JDK层面对Synchronized功能的一种模拟实现,ReentrantLock的功能就是对照着Synchronized的思路来实现的。当你熟悉ReentrantLock的实现思路之后,再了解了Synchronized锁升级过程,你会发现两者也是很相似的。

ReentrantLock,提供了独占锁、公平/非公平、可重入锁的功能。

ReentrantLock的主要功能都是基于AbstractQueuedSynchronizer实现的,关于AbstractQueuedSynchronizer的介绍请戳《JUC之AbstractQueuedSynchronizer基本介绍

 JUC之ReentrantLock


█ state

在AbstractQueuedSynchronizer中提供了一个字段state,用于记录同步的状态。

private volatile int state;

ReentrantLock使用state记录锁的状态,当state=0时,表示当前锁没有被任何的线程获取持有。当有一个线程获取持有了锁之后,state的值会加1。ReentrantLock是支持锁的重入的,即当前持有的锁多次获取锁时(多次调用lock获取锁的方法),每调用一次,state的值会随着加1。相反释放锁的时候,每释放锁一次(调用unlock方法),state的值会随着减1,直到state=0。

在ReentrantLock中,state的值范围为:>=0

只有当state=0时,锁才能被某个线程获取到。


█ 公平/非公平锁

ReentrantLock既支持公平锁也支持非公平锁。公平锁是当一个线程需要获取锁的时候,它可以直接去获取锁而不用关心在自己的之前是否已经有其他线程在等待获取锁(在AbstractQueuedSynchronizer等待队列中已存在其他线程),即有种先来后到的感觉。获取锁需要排队,先来的先去获取锁。非公平锁呢就没有这种先来后到的感觉了,一个线程需要获取锁,它就可以直接去抢这边锁,完全不用关心在自己之前是否已经有等待的线程了。

ReentrantLock中定义了两个内部类,FairSyncNonfairSync分别来实现公平锁与非公平锁的功能。FairSync与NonfairSync都继承了Sync,而Sync又继承了AbstractQueuedSynchronizer。

JUC之ReentrantLock

  • Sync

Sync继承了AbstractQueuedSynchronizer,在关于AbstractQueuedSynchronizer的相关介绍中提到自定义锁的话需要重写AbstractQueuedSynchronizer提供的tryAcquire、与tryRelease方法分别用来自定义获取锁与释放锁的逻辑。在Sync中提供了对tryRelease的重写,因此不管是公平锁还是非公平锁,其释放锁的逻辑都是相同的。tryAcquire获取锁的逻辑就有区别了,所以需要不同的子类去实现,公平锁实现公平地获取锁的逻辑,非公平锁实现非公平地获取锁的逻辑。

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

    // 获取锁入口
    abstract void lock();

    // 定义了非公平锁获取方法
    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            if (compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
   
    // 重写了AbstractQueuedSynchronizer的tryRelease方法
    // 自定义释放锁逻辑
    protected final boolean tryRelease(int releases) {
        // state-1
        int c = getState() - releases;
        // 如果当前线程不是持有锁的线程会报错,即只有持有锁的线程才能释放锁
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        boolean free = false;
        // 如果state==0表示所有的锁都被释放了
        if (c == 0) {
            free = true;
            setExclusiveOwnerThread(null);
        }
        // 更新state值
        setState(c);
        return free;
    }
    
    // 判断当前线程是否持有锁
    protected final boolean isHeldExclusively() {
        return getExclusiveOwnerThread() == Thread.currentThread();
    }
    
    // 获取ConditionObject,支持线程的await与signal
    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); // reset to unlocked state
    }
}

(1)nonfairTryAcquire

Sync只提供了非公平锁获取的逻辑,即nonfairTryAcquire方法。

final boolean nonfairTryAcquire(int acquires) {
    // 获取当前线程
    final Thread current = Thread.currentThread();
    // 查看锁的状态
    int c = getState();
    // 当state=0时,表示锁没有被占用,即可去获取锁
    if (c == 0) {
        // 使用CAS操作去改变state的值,即将state的值变成1
        if (compareAndSetState(0, acquires)) {
            // 如果CAS操作成功,标记当前持有锁的线程为当前线程。
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // state!=0表示已经有线程持有锁了
    // 判断当前线程是否是持有锁的线程,只有持有锁的线程才能继续获取锁(锁的重入)
    else if (current == getExclusiveOwnerThread()) {
        // state的值加1
        int nextc = c + acquires;
        // 防止重入的次数超过了Integer的最大值造成内存溢出。
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        // 更新state的值    
        setState(nextc);
        return true;
    }
    // 其他情况直接返回false,表示获取锁失败
    return false;
}

JUC之ReentrantLock

①第9行的compareAndSetState(0, acquires)会存在更改值失败返回false的情况,在多线程并发的情况下,在当前线程执行compareAndSetState(0, acquires)方法之前,可能其他线程已经执行过了这个方法,此时的state被更改为1,和期望值0不相等,CAS失败。

setExclusiveOwnerThread

setExclusiveOwnerThread是AbstractOwnableSynchronizer提供的方法,AbstractOwnableSynchronizer是AbstractQueuedSynchronizer的父类。其作用是将线程标记成持有锁的线程,即当前锁被指定的线程持有。

private transient Thread exclusiveOwnerThread;
protected final void setExclusiveOwnerThread(Thread thread) {
    exclusiveOwnerThread = thread;
}
  • NonfairSync

NonfairSync实现了非公平锁的功能。NonfairSync重写了AbstractQueuedSynchronizer提供的tryAcquire方法来自定义实现非公平锁的获取逻辑。NonfairSync啥也没干,直接调用了Sync的nonfairTryAcquire的方法。

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);
    }
    
    // 重写AbstractQueuedSynchronizer的方法自定义获取锁的逻辑
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}
  • FairSync

FairSync实现了公平锁功能。也是通过重写AbstractQueuedSynchronizer的方法tryAcquire来自定义获取公平锁逻辑。

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

    final void lock() {
        acquire(1);
    }
    // 重写AbstractQueuedSynchronizer的tryAcquire方法
    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;
    }
}

比较FairSync与NonfairSync的tryAcquire方法(NonfairSync的tryAcquire即Sync的nonfairTryAcquire方法),两者就一处的区别,在FairSync的tryAcquire多了一个!hasQueuedPredecessors()代码逻辑(上面代码的第12行)。因此公平与非公平就体现在这里了。

(1)hasQueuedPredecessors

hasQueuedPredecessors是AbstractQueuedSynchronizer中提供的方法,用于判断等待队列中,在当前线程的前面是否有其他线程。

public final boolean hasQueuedPredecessors() {
    Node t = tail; 
    Node h = head;
    Node s;
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

█ ReentrantLock

ReentrantLock实现锁功能,借助了NonfairSync与FairSync。

(1)sync

ReentrantLock中持有一个Sync类型的sync字段,用于标记当前锁的类型,是公平锁还是非公平锁。

private final Sync sync;

(2)构造器

ReentrantLock提供了两个构造器,使用空参的构造器,默认创建的是NonfairSync对象,即默认为非公平锁。

 

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

可指定参数true或false来设置锁的类型。true则创建FairSync对象实现公平锁功能,false则创建NonfairSync对象实现非公平锁功能。

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

(3)lock

lock方法为获取锁的入口,将功能转发到Sync的lock方法,即NonfairSync或FairSync的lock方法。

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

NonfairSync#lock

非公平锁一上来就去获取锁,如果失败则执行acquire逻辑。

final void lock() {
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

acquire是AbstractQueuedSynchronizer中提供的方法,主要提供了获取锁以及获取锁失败的入队、休眠处理逻辑(具体内容请戳《JUC之AbstractQueuedSynchronizer基本介绍》)

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

在acquire方法中,只有tryAcquire是由子类去自定义重写的。NonfairSync重写了tryAcquire方法。

FairSync#lock

调用acquire方法,会调用FairSync重写的tryAcquire方法。

final void lock() {
    acquire(1);
}

(4)unlock

unlock释放锁。逻辑转发给了Sync的release方法。

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

独占锁

在ReentrantLock中不管是公平锁还是非公平锁,其都是独占锁,即同时只允许一个线程获取到锁(也可以说一个锁只能同时会一个线程持有),只有锁不被任何一个线程持有的时候,其他线程才有可能获取到锁。查看NonfairSync与 FairSync的方法tryAcquire,它们都有逻辑,即当锁已被线程某个线程持有,而此时希望获取锁的线程如果不是当前持有锁的线程,tryAcquire或返回false,即获取锁失败,此时这个希望获取锁的线程或进入同步等待阻塞队列,等待有机会获取到锁。

 

可重入锁

ReentrantLock中的锁是支持可重入的,即已经获取到的锁的线程,其可以多次获取锁。ReentrantLock使用state来记录加锁的次数。每调用一次lock方法获取锁成功,state值会加1。每调用一次unlock方法释放锁成功,state值会减1,直到state=0表示该线程完全释放了锁,锁不再被当前线程持有。当state=0时,线程才有可能获取锁成功,因此调用lock的次数要与unlock的次数相同。调用了几次lock,就需要调用几次unlock。

 

上一篇:JUC工具包的加法、减法计数器


下一篇:关于Java JUC