锁对象
用ReentrantLock保护代码块基本结构:
public class Bank {
private Lock bankLock = new ReentrantLock();
...
public void transfer(int from, int to, int amount) {
bankLock.lock();
try {
...
} finally {
bankLock.unlock(); //确保临界区代码抛出异常,锁必须被释放
}
}
}
每个Bank对象都有自己的ReentrantLock对象。访问不同的Bank对象,会得到不同的锁对象。
锁是可重入的,获得锁的线程可以重复获得相同的锁,调用其他使用相同锁的方法,锁用一个持有计数来跟踪对lock方法的嵌套调用。
条件对象
线程进入临界区,发现需要某条件满足后才能执行。需要使用一个条件对象来管理获得锁但无法执行的线程。
一个锁对象可以有一个或多个相关的条件对象,使用newCondition方法获得一个条件对象。
condition.await(); 线程阻塞,放弃锁,进入该条件的等待集
condition.signalAll(); 唤醒所有等待该条件的线程,解除阻塞状态,线程试图重新获得锁
class Bank
{
private final double[] accounts;
private Lock bankLock;
private Condition sufficientFunds;
public Bank() {
...
bankLock = new ReentrantLock();
sufficientFunds = bankLock.newCondition();
}
public void transfer(int from, int to, int amount) throws InterruptedException{
bankLock.lock();
try {
while (accounts[from] < amount)
sufficientFunds.await(); //账户钱不够线程阻塞
//transfer
sufficientFunds.signalAll();
} finally {
bankLock.unlock();
}
}
}
锁测试与超时
等待锁超时
线程调用lock方法获取另一线程持有的锁时,很可能发生阻塞。
tryLock方法试图申请一个锁,若成功获得锁则返回true,否则返回false,线程可以立即离开去做其他事情。
if (myLock.tryLock()) {
try { ... }
finally {myLock.unlock();}
} else {
// 未获得锁
...
}
调用tryLock时,可以使用超时参数
if (myLock.tryLock(100, TimeUnit.MILLISECONDS))
或调用lockInterruptibly方法,相当于超时参数设为无限。
lock方法不能被中断。如果一个线程在等待获得锁时被中断(interrupt()),中断线程在获得锁之前会一直处于阻塞状态。若出现死锁,lock方法就无法终止。
如果调用的是带有超时参数的tryLock或lockInterruptibly,若线程在进入此方法前或在等待锁期间被中断会抛出InterruptedException异常。允许程序打破死锁。
等待条件超时
等待一个条件时,也可以设置超时参数
myCondition.await(100, TimeUnit.MILLSECONDS);
当线程被另一个线程的signalAll或signal激活,或超时时限达到,或线程被中断时,await方法将返回。当等待的线程被中断时,会抛出InterruptedException异常。