锁对象
用 ReentrantLock 保护代码块的基本结构如下:
myLock.lock(); // a ReentrantLock object try { critical section } finally { myLock.unlock();// make sure the lock is unlocked even if an exception is thrown }
这一结构确保任何时刻只有一个线程进人临界区。一旦一个线程封锁了锁对象, 其他任 何线程都无法通过 lock语句。当其他线程调用 lock 时,它们被阻塞,直到第一个线程释放 锁对象。
让我们使用一个锁来保护 Bank类的 transfer 方法。
public class Bank { private Lock bankLock = new ReentrantLock();// ReentrantLock implements the Lock interface ... public void transfer(int from, int to, int amount) { bankLock.lock(); try { System.out.print(Thread.currentThread()); accounts[from] -= amount; System.out.printf(" X10.2f from %A to Xd", amount, from, to); accounts[to] += amount; System.out.printf(" Total Balance: X10.2fXn", getTotalBalance()); }finally{ banklock.unlock(); } } }
假定一个线程调用 transfer, 在执行结束前被剥夺了运行权。假定第二个线程也调用 transfer, 由于第二个线程不能获得锁,将在调用 lock 方法时被阻塞。它必须等待第一个线程 完成 transfer 方法的执行之后才能再度被激活。当第一个线程释放锁时, 那么第二个线程才 能开始运行(见图 14-5 )。

锁是可重入的, 因为线程可以重复地获得已经持有的锁。锁保持一个持有计数(hold count) 来跟踪对 lock 方法的嵌套调用。线程在每一次调用 lock 都要调用 unlock 来释放锁。 由于这一特性, 被一个锁保护的代码可以调用另一个使用相同的锁的方法。
条件对象
通常, 线程进人临界区,却发现在某一条件满足之后它才能执行。要使用一个条件对 象来管理那些已经获得了一个锁但是却不能做有用工作的线程。在这一节里, 我们介绍 Java 库中条件对象的实现。(由于历史的原因, 条件对象经常被称为条件变量(conditional variable)。 )
现在来细化银行的模拟程序。我们避免选择没有足够资金的账户作为转出账户。注意不 能使用下面这样的代码:
if (bank.getBalance(fron) >= amount) bank.transfer(fro«, to, amount);
当前线程完全有可能在成功地完成测试,且在调用 transfer 方法之前将被中断。
if (bank.getBalance(from) >= amount) // thread night be deactivated at this point
bank.transfer(from, to, amount); 在线程再次运行前,账户余额可能已经低于提款金额。必须确保没有其他线程在本检査余额 与转账活动之间修改余额。通过使用锁来保护检査与转账动作来做到这一点:
public void transfer(int from, int to,int amount) { bankLock.1ock(); try { while (accounts[from] < amount) { // wait ... } // transfer funds ... } finally { bankLock.unlock(); } }
现在,当账户中没有足够的余额时, 应该做什么呢? 等待直到另一个线程向账户中注入 了资金。但是,这一线程刚刚获得了对 bankLock 的排它性访问, 因此别的线程没有进行存 款操作的机会。这就是为什么我们需要条件对象的原因。 一个锁对象可以有一个或多个相关的条件对象。你可以用 newCondition 方法获得一个条 件对象。习惯上给每一个条件对象命名为可以反映它所表达的条件的名字。例如,在此设置 一个条件对象来表达“ 余额充足” 条件。
class Bank { private Condition sufficientFunds; public Bank() { sufficientFunds = bankLock.newCondition(); } }
如果 transfer 方法发现余额不足,它调用
sufficientFunds.await();
当前线程现在被阻塞了,并放弃了锁。我们希望这样可以使得另一个线程可以进行增加 账户余额的操作。
等待获得锁的线程和调用 await 方法的线程存在本质上的不同。一旦一个线程调用 await 方法,它进人该条件的等待集。当锁可用时,该线程不能马上解除阻塞。相反,它处于阻塞 状态,直到另一个线程调用同一条件上的 signalAll 方法时为止。 当另一个线程转账时, 它应该调用
sufficientFunds,signalAll();
实际上, 正确地使用条件是富有挑战性的。在开始实现自己的条件对象之前, 应该考虑 使用 14.10 节中描述的结构。
//程序清单 14-7 synch/Bank.java package synch; import java.util.concurrent.locks.*; /** * A bank with a number of bank accounts that uses locks for serializing access. * @version 1.30 2004-08-01 * @author Cay Horstmann */ public class Bank { private final double[] accounts; private Lock bankLock; private Condition sufficientFunds; /** * Constructs the bank. * @param n the number of accounts * @param initialBalance the initial balance for each account */ public Bank(int n, double initialBalance) { accounts = new double[n]; for (int i = 0; i < accounts.length; i++) accounts[i] = initialBalance; bankLock = new ReentrantLock(); sufficientFunds = bankLock.newCondition(); } /** * Transfers money from one account to another. * @param from the account to transfer from * @param to the account to transfer to * @param amount the amount to transfer */ public void transfer(int from, int to, double amount) throws InterruptedException { bankLock.lock(); try { while (accounts[from] < amount) sufficientFunds.await(); System.out.print(Thread.currentThread()); accounts[from] -= amount; System.out.printf(" %10.2f from %d to %d", amount, from, to); accounts[to] += amount; System.out.printf(" Total Balance: %10.2f%n", getTotalBalance()); sufficientFunds.signalAll(); } finally { bankLock.unlock(); } } /** * Gets the sum of all account balances. * @return the total balance */ public double getTotalBalance() { bankLock.lock(); try { double sum = 0; for (double a : accounts) sum += a; return sum; } finally { bankLock.unlock(); } } /** * Gets the number of accounts in the bank. * @return the number of accounts */ public int size() { return accounts.length; } }