在大多数实际的多线程应用中,两个或多个以上的线程需要共享对同一数据的存取,如果两个线程存取相同的对象,并且每个线程都调用了一个修改该对象状态的方法,将会发生线程彼此踩了对方的脚。根据各线程访问数据的次序,可能会产生讹误的对象。这样一个情况通常称为竞争条件。
为了避免多线程引起的对共享数据的讹误,必须要做到同步存取。
下面将模拟一个有若干账户的银行。随机地生成在这些账户之间转移钱款的交易。每一个账户有一个线程。每一笔交易中,会从线程所服务的账户中随机转移一定目的钱款到另一个随机账户。
模拟代码很直观。我们具有transfer方法的Bank类。该方法从一个账户转移一定数目的钱款到另一个账户(还没有考虑负的账户余额)。如下是Bank类的transfer方法的代码。
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 from %d to %d", amount, from, to);
accounts[to] += amount;
System.out.printf("Total Balance: %10.2f%n", getTotalBalance());
}
下面是TransferRunnable类的代码。它的run方法不断地从一个固定的银行账户取出钱款。在每一次迭代中,run方法随机选择一个目标账户和一个随机账户,调用bank对象的transfer方法,然后睡眠。
<pre name="code" class="cpp"><pre name="code" class="java">class TransferRunnable implements Runnable
{
...
public void run()
{
try
{
while (true)
{
int toAccount = (int) (bank.size() * Math.random());
double amount = maxAmount * Math.random();
bank.transfer(fromAccount, toAccount, maxAmount);
Thread.sleep((int)(DELAY * Math.random()));
}
} catch (InterruptedException e)
{
}
}
}
当这个模拟程序运行时,不清楚在某一时刻某一银行账户中有多少钱。但是,知道所有账户的总金额应该保持不变,因为所做的一切不过是从一个账户转移钱款到另一账户。
在每一次交易的结尾,transfer方法重新计算总值并打印出来。
具体实现代码如下:
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 from %d to %d", amount, from, to);
accounts[to] += amount;
System.out.printf("Total Balance: %10.2f%n", getTotalBalance());
}
public double getTotalBalance()
{
double sum = 0;
for (double a : accounts)
sum += a;
return sum;
}
public int size()
{
return accounts.length;
}
}
</pre><pre name="code" class="java">public class TransferRunnable implements Runnable{
private Bank bank;
private int fromAccount;
private double maxAmount;
private int DELAY = 10;
public TransferRunnable(Bank b, int from, double max)
{
bank = b;
fromAccount = from;
maxAmount = max;
}
public void run()
{
try
{
while (true)
{
int toAccount = (int) (bank.size() * Math.random());
double amount = maxAmount * Math.random();
bank.transfer(fromAccount, toAccount, maxAmount);
Thread.sleep((int)(DELAY * Math.random()));
}
} catch (InterruptedException e)
{
}
}
}
public class UnsynchTest {
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);
int i;
for (i = 0; i < NACCOUNTS; i++)
{
TransferRunnable r = new TransferRunnable(b, i, INITIAL_BALANCE);
Thread t = new Thread(r);
t.start();
}
}
}