ReentrantLock
ReentrantLock是JDK内置的显示锁,相对于隐式锁synchronized,ReentrantLock提供了比synchronized更精细化的锁控制。通过情况下,我们会这样使用显示锁……
lock.lock();
try {
//do something
} finally {
lock.unlock();
}
ReentrantLock是可重入锁,意思是当线程A得到了锁后,还可以继续拿到该锁。
ReentrantLock是独占锁,同时只能有一个线程获取该锁。
ReentrantLock和AbstractQueuedSynchronizer的关系
ReentrantLock其实是通过内部类代理实现了锁的功能,内部类继承自AbstractQueuedSynchronizer,后面会简写为AQS,锁的核心实现都来自于AQS。AQS中的tryAcquire、tryRelease、tryAcquireShared、tryReleaseShared都抛出异常,子类如果需要,实现这几个方法即可。
先来简单看下ReentrantLock的声明:
public class ReentrantLock implements Lock, java.io.Serializable {
//同步器的实现,ReentrantLock都是通过sync代理AQS的
private final Sync sync;
//字段……
//方法……
//Sync继承了AQS
abstract static class Sync extends AbstractQueuedSynchronizer {
//
}
//非公平锁
static final class NonfairSync extends Sync {
}
//公平锁
static final class FairSync extends Sync {
}
}
ReentrantLock实现了Lock接口,锁的功能是通过内部静态类Sync、NonfairSync、FairSync实现的。
公平锁和非公平锁
公平锁和非公平锁的区别在于获取锁操作过程的差异,后面讲到代码时还会详细介绍,这也是锁的重点,这里简单说明下。
公平锁:检查当前线程的前置结点是否是头结点,如果不是则会继续排队等待,否则获取锁。这种获取锁的方式可以保证队列的FIFO特性,先排队的线程先获取锁,公平地获取锁。但是这种方式效率不高,假如头结点的线程释放了锁,还要去唤醒后一个线程去获取锁,线程上下文切换,成本较高。下图是公平锁的获取示意图:
图1:公平锁的获取
非公平锁:获取锁的线程首先去尝试获取锁,如果没有获取成功则会去等待队列排队等待。这种方式首先去尝试获取锁。假如头结点的线程释放了锁,若此时另一个线程恰好去获取非公平锁,那么该线程可以竞争到该锁,免去了释放锁的线程去唤醒后续线程从而引起线程上下文切换。下图是非公平锁的获取示意图:
图2:非公平锁的获取
看下ReentrantLock的构造函数:
//默认构造函数创建非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
//通过参数指定创建公平锁还是非公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
ReentrantLock默认创建的是非公平锁,这是因为非公平锁在实际使用中效率更高。另外还可以通过ReentrantLock提供的带参数的构造函数指定创建公平锁还是非公平锁。
非公平锁的lock方法
前面说到,我们一般会通过调用ReentantLock的lock方法去获取锁,先看下lock的源码:
public void lock() {
sync.lock();
}
lock方法的源码非常简单,锁的功能通过sync代理了。当我们执行如下代码时,将会创建一个非公平锁:
//创建一个非公平锁
Lock lock = new ReentrantLock();
非公平锁是通过内部类NoneFairSync实现的,ReentrantLock有3个静态内部类,分别是Sync、FairSync和NonfairSync。下图是ReentrantLock的简单类图:
图3:ReentrantLock简单类图
该图是ReentrantLock的简单类图框架,ReentrantLock的内部类Sync继承自AQS,FairSync和NonfairSync分别继承自Sync,实现了公平锁和非公平锁。
ReentrantLock的lock方法其实是调用Sync的lock方法,而Sync的lock方法是个抽象方法:
abstract void lock();
因此子类需要去实现该抽象方法,非公平锁NonfairSync的实现如下:
//非公平锁继承了Sync
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
//锁的状态是0代表当前锁是空闲的
//比较锁的状态是否是0,原子的设置为1,若设置成功,设置独占线程为当前线程
//此处体现了非公平锁的特点,先尝试获取锁,获取失败再去队列排队获取锁
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
//队列排队获取锁
acquire(1);
}
//稍后会讲解该代码
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
可以看到非公平锁的lock实现思路,首先直接去获取锁,若获取锁成功,将当前锁的占有者设置为当前线程。否则调用acquire方法获取锁。
这里先说明下comareAndSetState方法,类似这种CAS方法在JDK的锁框架中大量使用,JDK的锁正是通过CAS原子操作得以实现。