:) 楼上解释的很正确,也就是这么一个道理,比如我们平常生活中的一个赏识、比如你在银行有个账户,当然你的账户里面有钱你才能取出来钱,假如你的帐号有500块钱,有两条线程同时取钱。每条线程取500,那么假如其中一条线程在判断完账户余额是否大于取钱数时候暂停,切换到另外一条线程,那么这就有可能导致账户余额为负,这就是银行不希望看到的结果,那么我们就应该为这个取钱的操作进行同步,也就是说,不管任何时候只有一条线程进入这个取钱的操作代码快中
:) 比如有这么一个需求,加入现在系统中有两条线程,这两条线程分别代表存款者和取款者---现在假设系统有一种特殊的要求,系统要求存款者和取钱者不断的重复取钱、存钱操作、并且要求每当存款者将钱存入账户之后、取钱着就立即取出这笔钱,不允许存款者连续两次取钱,也不允许取钱者两次取钱,
这个时候我们就应该为这两个取钱和存钱的方法进行同步。比如下面程序我们实现这个功能:
1、Account账户类---提供了取钱和存钱的两个方法,还要提供一个flag旗标来表示账户是不是已经有存款了[code=java]package cn.itcast.heima
public class Account
{
//用来标识账户号
private String accountNo;
//用来表示账户余额
private double balance;
//标识账户中是否已有存款的旗标
private boolean flag = false;
//无参和有参构造器
public Account(){}
public Account(String accountNo , double balance)
{
this.accountNo = accountNo;
this.balance = balance;
}
public void setAccountNo(String accountNo)
{
this.accountNo = accountNo;
}
public String getAccountNo()
{
return this.accountNo;
}
//不提供balance属性的setter方法,防止程序直接修改这个属性
public double getBalance()
{
return this.balance;
}
//因为有有两个方法可能同时需要并发修改Account类的balance属性,所以我们用synchronized 修饰这两个方法
public synchronized void draw(double drawAmount)
{
try
{
//如果flag为假,表明账户中还没有人存钱进去,则取钱方法阻塞
if (!flag)
{
wait();
}
else
{
//执行取钱
System.out.println(Thread.currentThread().getName() +
" 取钱:" + drawAmount);
balance -= drawAmount;
System.out.println("账户余额为:" + balance);
//将标识账户是否已有存款的旗标设为false。
flag = false;
//唤醒其他线程
notifyAll();
}
}
catch (InterruptedException ex)
{
ex.printStackTrace();
}
}
public synchronized void deposit(double depositAmount)
{
try
{
//如果flag为真,表明账户中已有人存钱进去,则存钱方法阻塞
if (flag)
{
wait();
}
else
{
//执行存款
System.out.println(Thread.currentThread().getName() +
" 存款:" + depositAmount);
balance += depositAmount;
System.out.println("账户余额为:" + balance);
//将表示账户是否已有存款的旗标设为true
flag = true;
//唤醒其他线程
notifyAll();
}
}
catch (InterruptedException ex)
{
ex.printStackTrace();
}
}
//重写这个类的hashCode和equals方法
//一般都要重写这两个方法,论坛中有帖子已经说明为什么了,
public int hashCode()
{
return accountNo.hashCode();
}
public boolean equals(Object obj)
{
if (obj != null && obj.getClass() == Account.class)
{
Account target = (Account)obj;
return target.getAccountNo().equals(accountNo);
}
return false;
}
}[/code]关于equals方法和hashCode方法可以参考这个帖子http://bbs.itheima.com/thread-696-1-1.html
以后我们还会学到同步锁其实功能都差不多一样,但是同步锁功能比这个更强大,
2、下面定义两条线程,其中的线程执行体分别执行上面Account中的取钱和存钱方法:
DrawThread ---模拟取钱的操作[code=java]package cn.itcast.heima
public class DrawThread extends Thread
{
//模拟用户账户
private Account account;
//当前取钱线程所希望取的钱数
private double drawAmount;
public DrawThread(String name , Account account ,
double drawAmount)
{
super(name);
this.account = account;
this.drawAmount = drawAmount;
}
//重复50次执行取钱操作
public void run()
{
for (int i = 0 ; i < 50 ; i++ )
{
account.draw(drawAmount);
}
}
}[/code]DepositThread ---模拟存钱的操作[code=java]package cn.itcast.heima
public class DepositThread extends Thread
{
//模拟用户账户
private Account account;
//当前取钱线程所希望存款的钱数
private double depositAmount;
public DepositThread(String name , Account account ,
double depositAmount)
{
super(name);
this.account = account;
this.depositAmount = depositAmount;
}
//重复50次执行存款操作
public void run()
{
for (int i = 0 ; i < 50 ; i++ )
{
account.deposit(depositAmount);
}
}
}[/code]3、主程序可以启动多条取钱线程和存钱线程---[code=java]package cn.itcast.heima
public class TestDraw
{
public static void main(String[] args)
{
//创建一个账户
Account acct = new Account("530335264" , 0);
new DrawThread("取钱者" , acct , 800).start();
new DepositThread("存款者甲" , acct , 812).start();
new DepositThread("存款者乙" , acct , 812).start();
}
}[/code]运行结果我们可以看到:[code=java]存款者甲 存钱:812.0
账户余额:812.0
取钱者 取钱:812.0
账户余额:0.0
...............
取钱者 取钱:812.0
账户余额:0.0
存款者丙 存钱:812.0
账户余额:812.0
取钱者 取钱:812.0
账户余额:0.0
.........
存款者乙 存钱:812.0
账户余额:812.0 ---------------程序阻塞[/code]最后程序阻塞:因为有两个线程存钱,循环100次 ,一个取钱线程操作50次,所以最后取钱线程运行结束,程序阻塞--但不是死锁
[ 本帖最后由 詹季春 于 2011-08-02 01:41 编辑 ] |