ReentrantLock简介
ReentrantLock
是Lock
的子类,它是可重入的锁,即已经获取锁的线程可以重复获取锁,同时它还支持公平锁和非公平锁。如果保证先请求资源的线程总是先获得锁,那么就是公平锁,否则是非公平锁。非公平锁的效率要比公平锁高,不过非公平锁可能存在线程饥饿问题。ReentrantLock
的使用很简单,示例如下
public class Sequence {
private int value;
Lock lock = new ReentrantLock();
public int getNext() {
lock.lock();
try {
int a = value ++;
return a;
}finally {
//释放锁的代码建议防止finally块当中
lock.unlock();
}
}
public static void main(String[] args) {
final Sequence s = new Sequence();
new Thread(new Runnable() {
@Override
public void run() {
while(true) {
System.out.println(Thread.currentThread().getName() +
" " + s.getNext());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while(true) {
System.out.println(Thread.currentThread().getName() +
" " + s.getNext());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
ReentrantLock源码解析
ReentrantLock
内部维护着Sync
类型的成员变量,Sync
是定义在ReentrantLock
中的静态内部类,同时Sync
是AbstractQueuedSynchronizer
的子类。ReentrantLock
锁功能的实现就是借助AQS
来实现的。
public class ReentrantLock implements Lock, java.io.Serializable {
private final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer {
......
}
}
首先,我们先来看看ReentrantLock
的构造方法,ReentrantLock
提供了两个构造方法,一个无参的,另一个带一个boolean
类型的参数。
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
构造器的工作就是在实例化sync
成员变量,分有FairSync
和NonFairSync
两种,这两个类都是Sync
的子类,即这两个类都是AQS
的子类。通过FairSync
让ReentrantLock
变成公平锁,NonFairSync
让ReentrantLock
变成非公平锁。
如果能让先竞争资源的线程获取锁,这样的锁称为公平锁,否则称为非公平锁。公平锁的效率通常比非公平锁的效率要低很多,但是非公平锁可能会让线程产生"饥饿等待"问题。下面我们重点来看看非公平锁是如何实现的。
加锁实现
ReentrantLock
一般通过lock()方法实现加锁,而lock方法的内部会委托给sync
的lock
方法,而sync
是个队列同步器(AQS
),它又分公平和非公平两种实现。下面来看看非公平锁的实现,即NonfairSync
的lock
方法
final void lock() {
// 通过CAS尝试获取同步资源,即将同步变量由0置为1
if (compareAndSetState(0, 1))
// 成功获取同步资源之后,设置当前线程,标明当前线程持有锁
setExclusiveOwnerThread(Thread.currentThread());
else
// acquire方法为AbstractQueuedSynchronizer的模板方法
acquire(1);
}
acquire
的实现中会先调用tryAcquire
尝试获取资源,获取失败就自旋阻塞等待。NonfairSync
和FairSync
都重写了tryAcquire
方法,实现代码如下
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
// 获取同步状态
int c = getState();
//如果同步状态为0,表明当前锁没有被任何线程锁持有
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 实现锁的可重入(判断当前持有锁的线程是否等于当前线程)
else if (current == getExclusiveOwnerThread()) {
// 同步状态+1, 这里acquires的实际值为1
int nextc = c + acquires;
// 判断nextc是否越界,伟大的程序员就是厉害!
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
当已经获取锁的线程再次尝试获取锁,即锁重入,重入几次同步状态(state)的值就加几,释放锁的时候要将state减为0时才能成功释放锁。
公平锁获取的代码几乎和非公平锁一样,区别就是公平锁在获取锁之前要先判断当前线程之前是否还有其他线程(来得比当前线程早)等待获取锁,如果当前线程还有其他线程那么当前线程需要继续等待,来得早的线程先获得锁。
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 通过hasQueuedPredecessors判断之前是否还存在等待线程
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
......
}
hasQueuedPredecessors()
是AQS
中的方法,它通过判断内部的FIFO队列当前线程所在的节点是否有前驱节点来实现,如果存在前驱节点则返回true
。由于是FIFO队列,所以前驱节点要比当前节点早入队列。
释放锁实现
ReentrantLock
通过unlock()
方法来释放锁,unlock()
会调用sync
的release方法。release(int arg)
同样也是AQS
的模板方法,它的内部会调用tryRelease
方法尝试释放同步资源。NonfairSync
和FairSync
共用同个tryRelease
方法(定义在Sync
内部类中)。
protected final boolean tryRelease(int releases) {
// 当前状态-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);
}
setState(c);
return free;
}
我们可以看到tryRelease
没有使用什么同步机制,这是因为能成功获取锁的线程只有一个,所以释放锁的时候,不会存在多线程竞争的情况。