我们现阶段还是注重好代码的质量,执行质量就不多说啦,当然这些都是题外话,想和各位分享!接下来还是进入正题。
在大多数实际的多线程应用中,两个或两个以上的线程需要共享对同一数据的存取。如果两个线程存取相同的对象,并且每一个线程都调用了修改该对象的方法,将会发生什么?可能就会产生错误的对象。这样的情况通常称为竞争条件。
我们来模拟一个有若干账户的银行,随机在这些账户之间转账,每一个账户一个线程。每一次交易,会从线程所服务的账户中随机转移一定的金额到另一个账户中。
这是Bank类中转账的方法:
public void transfer(int from, int to, double amount) { if (accounts[from] < amount) { return; } System.out.print(Thread.currentThread()); accounts[from] -= amount; System.out.printf("转账金额: %10.2f 转出账户: %d 转入账户: %d", amount, from, to); accounts[to] += amount; System.out.printf(" 最后的金额: %10.2f%n", getTotalBalance()); }
@Override public void run() { while (true) { int toAccount = (int) (bank.size() * Math.random()); double amount = maxAmount * Math.random(); bank.transfer(fromAccount, toAccount, amount); try { Thread.sleep((int) (DELAY * Math.random())); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }
下面是例子的完整代码:
Bank类:
/** * @author XzZhao */ public class Bank { private final double[] accounts; public Bank(int n, double initialBalance) { accounts = new double[n]; for (int i = 0; i < accounts.length; i++) { accounts[i] = initialBalance; } } public void transfer(int from, int to, double amount) { if (accounts[from] < amount) { return; } System.out.print(Thread.currentThread()); accounts[from] -= amount; System.out.printf("转账金额: %10.2f 转出账户: %d 转入账户: %d", amount, from, to); accounts[to] += amount; System.out.printf(" 最后的金额: %10.2f%n", getTotalBalance()); } public double getTotalBalance() { double sum = 0; for (double a : accounts) { sum += a; } return sum; } public int size() { return accounts.length; } }
/** * @author XzZhao */ public class TransferRunnable implements Runnable { private final Bank bank; private final int fromAccount; private final double maxAmount; private final int DELAY = 10; public TransferRunnable(Bank bank, int fromAccount, double maxAmount) { super(); this.bank = bank; this.fromAccount = fromAccount; this.maxAmount = maxAmount; } @Override public void run() { while (true) { int toAccount = (int) (bank.size() * Math.random()); double amount = maxAmount * Math.random(); bank.transfer(fromAccount, toAccount, amount); try { Thread.sleep((int) (DELAY * Math.random())); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } }Test:
/** * @author XzZhao */ public class UnsynchBankTest { public static final int NACCOUNTS = 100; public static final double INITIAL_BALANCE = 1000; public static void main(String[] args) { Bank b = new Bank(NACCOUNTS, INITIAL_BALANCE); for (int i = 0; i < NACCOUNTS; i++) { TransferRunnable r = new TransferRunnable(b, i, INITIAL_BALANCE); Thread t = new Thread(r); t.start(); } } }程序出错:总金额发生变化
用ReentrantLock保护代码块的结构:
Lock myLock = new ReentrantLock(); myLock.lock(); try { // work code } finally { myLock.unlock(); }
使用一个锁来保护Bank类的transfer方法:
/** * @author XzZhao */ public class Bank { ... private final Lock bankLock = new ReentrantLock(); public void transfer(int from, int to, double amount) { if (accounts[from] < amount) { return; } bankLock.lock(); try { System.out.print(Thread.currentThread()); accounts[from] -= amount; System.out.printf("转账金额: %10.2f 转出账户: %d 转入账户: %d", amount, from, to); accounts[to] += amount; System.out.printf(" 最后的金额: %10.2f%n", getTotalBalance()); } finally { bankLock.unlock(); } } ... }
锁是可重入的,因为线程可以重复地获得已经持有的锁。锁保持一个持有计数来跟踪lock方法的嵌套调用。线程在每一次调用lock都要调用unlock来释放锁。所以,被一个锁保护的代码可以调用另一个使用相同的锁的方法。
我们来修改一下例子,我们使用条件对象来避免选择没有足够资金的账户作为转出账户。
Bank类:
/** * @author XzZhao */ public class Bank { private final double[] accounts; private final Lock bankLock; private final Condition sufficientFunds; 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(); // 获取一个和该锁相关的条件对象 } 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 转出账户: %d 转入账户: %d", amount, from, to); accounts[to] += amount; System.out.printf(" 最后的金额: %10.2f%n", getTotalBalance()); sufficientFunds.signalAll(); // 解除该条件的等待集中的所有线程的阻塞状态 } finally { bankLock.unlock(); } } public double getTotalBalance() { bankLock.lock(); try { double sum = 0; for (double a : accounts) { sum += a; } return sum; } finally { bankLock.unlock(); } } public int size() { return accounts.length; } }TransferRunnable类:
/** * @author XzZhao */ public class TransferRunnable implements Runnable { private final Bank bank; private final int fromAccount; private final double maxAmount; private final int DELAY = 10; public TransferRunnable(Bank bank, int fromAccount, double maxAmount) { super(); this.bank = bank; this.fromAccount = fromAccount; this.maxAmount = maxAmount; } @Override public void run() { try { while (true) { int toAccount = (int) (bank.size() * Math.random()); double amount = maxAmount * Math.random(); bank.transfer(fromAccount, toAccount, amount); Thread.sleep((int) (DELAY * Math.random())); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }