Lock接口是Java5提供有别于synchronized关键字的另一种独占锁机制。和synchronized相比有众多区别:
- 可非阻塞的锁获取
- 可中断的锁获取
- 可限时的锁获取
- 需要显式加锁和解锁
- 支持多个条件对象(Condition)
ReentrantLock实现了Lock接口,是一个可重入的独占锁,基于AQS(锁获取和锁释放委托给了AQS的实现子类),支持公平锁和非公平锁(默认),是一种基于互斥信号量的独占锁。
ReentrantLock的基本使用
保护临界区的方式
Lock lock = new ReentrantLock();
// ...
lock.lock();
try{
// 临界区代码
}finally{
// 必须显式地释放锁
lock.unlock();
}
锁必须显式地加锁和解锁,简单说就是判断互斥信号量的值。
条件对象的使用
//...
Condition conditon = lock.newCondition();
// ...
lock.lock();
try{
// 条件A
conditon.await();
// 条件B
condition.singal();
}finally{
// 必须显式地释放锁
lock.unlock();
}
和java内置锁的条件对象一样,条件对象的等待和唤醒必须在对应的锁持有的状态下,且和内置条件的wait一样,await会释放当前的锁。
公平锁和非公平锁
在获取锁的时候,如果当前锁没有被其他线程占用,就将锁对象的state原子更新(+1),并将此线程与锁对象绑定(使得后来尝试获取锁的线程发现锁已经被占用而阻塞);如果发现锁已经被其他线程占用,则构成Node对象加入同步队列中,在同步队列中会尝试自旋获取锁,如果失败会被park而阻塞,等待被唤醒。即AQS中获取独占锁的行为。
- 公平锁在尝试获取锁(lock.lock())的时候,会首先检查锁的同步队列,如果同步队列有线程在等待了,则此线程会直接去排队,不会尝试一次锁获取的动作(也就是更新state);
- 非公平锁在尝试获取锁时,不会检查同步队列,直接尝试检查和修改state状态;
也就是说,非公平锁可以插队,而公平锁不会插队。
独占锁原理
锁获取(非公平锁)
- 锁获取API
tryLock(); //
tryLock(long timeout);
lock();
lockInterruptibly();
lockInterruptibly很明显,可以响应中断。
- lock()方法逻辑
lock方法是常用的会阻塞的对中断不响应的锁获取方法。
ReentrantLock将锁处理的动作委托给了实现AQS的内部Sync类,concurrent包下的同步工具类基本都是使用此方式。由于由公平锁和非公平锁,所以将Sync抽象了,并派生两个子类:公平Sync和非公平Sync。
基于AQS的设计,Sync中只是实现了诸如tryAcquire和tryRelease这些模板方法(实质上就是互斥信号量的判断、原子更新).
lock方法的执行逻辑就是调用Sync的acquire方法(AQS的方法)-> 调用Sync的tryAcquire方法判断能否获取锁,否则进入AQS的同步队列。
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;
}
- tryLock
tryLock尝试获取锁,获取结果会立即返回,不会阻塞。且公平锁使用的是nonfairTryAcquire()方法,并非检查同步队列是否存在;
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
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;
}
- tryLock(long timeout)
使用的是AQS的tryAcquireNanos():
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
// tryAcquire依赖具体实现类
// 逻辑是先尝试tryAcquire获取一次,失败则doAcquireNanos限时重试
return tryAcquire(arg) ||
doAcquireNanos(arg, nanosTimeout);
}
// doAcquireNanos()和doAcquire相似,多了deadline判断。
private boolean doAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
final long deadline = System.nanoTime() + nanosTimeout;
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
// 自旋尝试获取锁
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return true;
}
// 检查超时
nanosTimeout = deadline - System.nanoTime();
if (nanosTimeout <= 0L)
return false;
// 限时阻塞
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
- lockInterruptibly()
在acquire中对中断状态进行判断,检测到中断就抛出异常。
锁释放
实现tryRelease方法,修改互斥信号量的值。参考AQS。
条件对象原理
await()和signal()方法是条件对象上的核心方法,前者是是当前在某个条件上阻塞等待并释放锁,后者是唤醒阻塞在此对象上的线程。
基于AQS的ConditionObject类
- await方法逻辑
持有锁的线程执行await方法,首先将当前线程构建成Node对象new Node(Thread.currentThread(), Node.CONDITION)
加入条件队列中,然后完全释放当前持有的锁(可能重入),
此时锁便可以被其他线程抢占,但是此时方法并没有执行结束,拥有执行时间片是会检查当前线程的节点释放在同步队列上(如果在条件队列上被唤醒,会被移到同步队列上),没有则继续阻塞,后面的事情就是和独占锁的lock操作相似了,即在同步队列上竞争锁或者阻塞。
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
// 加入条件队列
Node node = addConditionWaiter();
// 完全释放锁
int savedState = fullyRelease(node);
int interruptMode = 0;
// 轮询node是否被移到同步队列上
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
// 检查是否被中断
/**
* Checks for interrupt, returning THROW_IE if interrupted
* before signalled, REINTERRUPT if after signalled, or
* 0 if not interrupted.
* 如果返回的是THROW_IE,在reportInterruptAfterWait方法中会抛出异常,也就是如果在signal之前中断,会抛异常
*/
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
- signal
signal()会将条件队列的第一个节点移出条件队列,将其加入同步队列竞争锁。
public final void signal() {
// 说明执行signal需要在持有锁的前提下
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
// 将first节点移动:transferForSignal
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
/*
* If cannot change waitStatus, the node has been cancelled.
*/
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
/*
* Splice onto queue and try to set waitStatus of predecessor to
* indicate that thread is (probably) waiting. If cancelled or
* attempt to set waitStatus fails, wake up to resync (in which
* case the waitStatus can be transiently and harmlessly wrong).
*/
Node p = enq(node);
// 检查前驱节点的状态
int ws = p.waitStatus;
// 如果前驱节点的ws异常,直接唤醒线程
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
signalAll则是将条件队列上所有的节点都transferForSignal();
signal将线程转移到CLH同步队列之后,await方法中以下代码段会中断循环,继续执行:
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
这个while循环会退出,进入await的后续代码:
// 尝试获取锁,相当于lock操作
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
后续就是锁获取然后执行await后面的代码。
参考资料
[1] Java并发编程实战