使用的教材是java核心技术卷1,我将跟着这本书的章节同时配合视频资源来进行学习基础java知识。
day092 并发(四)(同步(一):竞争条件的一个例子、竞争条件详解、锁对象、条件对象)
同步
在大多数实际的多线程应用中,两个或两个以上的线程需要共享对同一数据的存取。如果两个线程存取相同的对象,并且每一个线程都调用了一个修改该对象状态的方法,将会发生什么呢?可以想象,线程彼此踩了对方的脚。根据各线程访问数据的次序,可能会产生i化误的对象。这样一个情况通常称为竞争条件(racecondition)。
1.竞争条件的一个例子
为了避免多线程引起的对共享数据的说误,必须学习如何同步存取。在本节中,你会看到如果没有使用同步会发生什么。在下一节中,将会看到如何同步数据存取。
在下面的测试程序中,模拟一个有若干账户的银行。随机地生成在这些账户之间转移钱款的交易。每一个账户有一个线程。每一笔交易中,会从线程所服务的账户中随机转移一定数目的钱款到另一个随机账户。
模拟代码非常直观。我们有具有transfer方法的Bank类。该方法从一个账户转移一定数目的钱款到另一个账户(还没有考虑负的账户余额)。如下是Bank类的transfer方法的代码。
public void transfer(int from, int to, double amount)
{
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());
}
这里是Runnable类的代码。它的run方法不断地从一个固定的银行账户取出钱款。在每一次迭代中,run方法随机选择一个目标账户和一个随机账户,调用bank对象的transfer方法,然后睡眠。
Runnable r = () -> {
try
{
while(true)
{
int toAccount = (int) (bank.size() * Math.random());
double amount = MAX_AMOUNT * Math.random();
bank.transfer(fromAccount, toAccount, amount);
Thread.sleep((int) (DELAY * Math.random()));
}
}
catch(InterruptedException e)
{
}
};
当这个模拟程序运行时,不清楚在某一时刻某一银行账户中有多少钱。但是,知道所有账户的总金额应该保持不变,因为所做的一切不过是从一个账户转移钱款到另一个账户。在每一次交易的结尾,transfer方法重新计算总值并打印出来。本程序永远不会结束。只能按CTRL+C来终止这个程序。
出现了错误。在最初的交易中,银行的余额保持在$100000,这是正确的,因为共100个账户,每个账户$1000。但是,过一段时间,余额总量有轻微的变化。当运行这个程序的时候,会发现有时很快就出错了,有时很长的时间后余额发生混乱。这样的状态不会带来信任感,人们很可能不愿意将辛苦挣来的钱存到这个银行。
下面是代码:
/**
*@author zzehao
*This program shows data corruption when multiple threads access a data structure.
*/
public class UnsynchBankTest
{
public static final int NACCOUNTS = 100;
public static final double INITIAL_BALANCE = 1000;
public static final double MAX_AMOUNT = 1000;
public static final int DELAY = 10;
public static void main(String[]