Locks in Java
Locks in Java
锁也是一种像同步块一样的线程同步机制,但是锁可能比Java的同步块更复杂。 锁(以及其他更高级的同步机制)是使用synchronized块创建的,因此我们不能完全摆脱synchronized关键字。
从Java 5开始,包java.util.concurrent.locks包含几个锁实现,因此您可能不必实现自己的锁。 但是你仍然需要知道如何使用它们,知道它们背后的实现理论仍然是有用的。 有关更多详细信息,请参阅我的java.util.concurrent.locks.Lock接口教程。
A Simple Lock
让我们从一个同步块开始看起:
public class Counter {
private int count = 0;
public int inc() {
synchronized(this) {
return ++count;
}
}
}
仅仅是一个++count操作, 却使用上了同步块…
相反,使用Lock而不是synchronized块,Counter类可以这样编写:
public class Counter {
private Lock lock = new Lock();
private int count = 0;
public int inc() {
lock.lock();
int newCount = ++count;
lock.unlock();
return newCount;
}
}
lock()方法锁定Lock实例,以便阻止所有调用lock()的线程,直到执行unlock()。
这是一个简单的上述Lock实现:
public class Lock {
private boolean isLocked = false;
public synchronized void lock() throws InterruptedException {
// 这里使用while而不是if,应该是为了防止虚假唤醒
while(isLocked) {
this.wait();
}
isLocked = true;
}
public synchronized void unlock() {
isLocked = false;
notify();
}
}
当线程完成关键部分中的代码(lock()和unlock()之间的代码)时,线程调用unlock()。 执行unlock()设置isLocked回到false,并通知(唤醒)在lock()方法中等待wait()调用的其中一个线程(如果有)。
Lock Reentrance
ava中的同步块是可重入的。 这意味着,如果Java线程进入同步的代码块,从而对监视器对象的锁定进行同步,则该线程可以重入在同一监视器对象上同步的其他Java代码块。 这是一个例子:
public class Reentrance {
public synchronized outer() {
inner();
}
public synchronized inner() {
// do something
}
}
注意outer()和inner()是如何声明同步的,在Java中它等同于synchronized(this)块。 如果一个线程调用outer(),则从outer()内部调用inner()没有问题,因为两个方法(或块)在同一个监视器对象(“this”)上同步。 如果线程已经在监视器对象上持有锁,则它可以访问在同一监视器对象上同步的所有块。 这称为重入。 线程可以重入已经拥有锁的任何代码块。
前面显示的锁实现不可重入。 如果我们像下面那样重写Reentrant类,则调用outer()的线程将在inner()方法中的lock.lock()内被阻塞。
public class Reentrant2 {
Lock lock = new Lock();
public void outer() {
lock.lock();
inner();
lock.unlock();
}
public void inner() {
lock.lock();
//do something
lock.unlock();
}
}
调用outer()的线程将首先锁定Lock实例。 然后它将调用inner()。 在inner()方法内部,线程将再次尝试锁定Lock实例。 这将失败(意味着线程将被阻止),因为Lock实例已经在outer()方法中被锁定。
调用outer()的线程将首先锁定Lock实例。 然后它将调用inner()。 在inner()方法内部,线程将再次尝试锁定Lock实例。 这将失败(意味着线程将被阻止),因为Lock实例已经在outer()方法中被锁定。
public class Lock {
boolean isLocked = false;
public synchronized void lock() throws InteruptedException {
while(isLocked) {
wait();
}
isLocked = true;
}
...
}
while循环(自旋锁)中的条件确定是否允许线程退出lock()方法。 目前的条件是isLocked必须为false才允许这样做,无论什么线程锁定它。
要使Lock类可重入,我们需要进行一些小改动:
public class Lock {
boolean isLocked = false;
Thread lockedBy = null;
int lockedCount = 0;
public synchronized void lock() throws InteruptedException {
Thread callingThread = Thread.currentThread();
while(isLocked && lockedBy != callingThread) {
wait();
}
isLocked = true;
lockedCount++;
lockedBy = callingThread;
}
public synchronized void unlock() {
if(Thread.currentThread() == this.lockedBy) {
lockedCount--;
if(lockedCount == 0) {
isLocked = false;
notify();
}
}
}
...
}
注意while循环(自旋锁)现在也如何考虑锁定Lock实例的线程。 如果锁被解锁(isLocked = false)或者调用线程是锁定Lock实例的线程,则while循环将不会执行,并且将允许调用lock()的线程退出该方法。
此外,我们需要计算锁被同一个线程锁定的次数。 否则,单次调用unlock()将解锁锁,即使锁被多次锁定也是如此。 我们不希望锁被解锁,直到锁定它的线程执行了与lock()调用相同数量的unlock()调用。
Lock类现在是可重入的了。
Lock Fairness
Calling unlock() From a finally-clause
使用Lock保护关键部分时,临界区可能会抛出异常,因此从finally子句中调用unlock()方法非常重要。 这样做可确保锁定已解锁,以便其他线程可以锁定它。 这是一个例子:
lock.lock();
try {
// do critical section code, which may throw exception.
} finally {
lock.unlock();
}
这个小构造确保锁定被解锁,以防从关键部分的代码中抛出异常。 如果未从finally子句内部调用unlock(),并且从临界区抛出异常,则Lock将永远保持锁定状态,从而导致在该Lock实例上调用lock()的所有线程无限制地停止。
翻译自: http://tutorials.jenkov.com/java-concurrency/locks.html
原作者: Jakob Jenkov
Orz Orz Orz