0038 Java学习笔记-多线程-传统线程间通信、Condition、阻塞队列、《疯狂Java讲义 第三版》进程间通信示例代码存在的一个问题

调用同步锁的wait()、notify()、notifyAll()进行线程通信

看这个经典的存取款问题,要求两个线程存款,两个线程取款,账户里有余额的时候只能取款,没余额的时候只能存款,存取款金额相同。相当于存取款交替进行,金额相同。

线程间通信,需要通过同一个同步监视器(也就是this或者显式的Object对象)调用通信方法,

Object有三个方法,可以用于线程间通信

wait()

当前线程等待,并释放同步锁

wait():无限期等待

wait(long timeout):等待timeout毫秒,

wait(long timeout,int nanos):等待timeout毫秒+nanos纳秒,nanos的范围[0,999999]

notify()

唤醒该同步监视器上的任意一个线程

只有当前线程调用了wait()方法后,被notify()唤醒的线程才会唤醒

notifyAll()

唤醒该同步监视器上的所有线程

只有当前线程调用了wait()方法后,被notify()唤醒的线程才会唤醒

看示例代码:

package testpack; public class Test1 { public static void main(String[] args){ Account ac=new Account("A123",0.0); new Deposit("存款者A",ac,325.0).start(); //这里开启两个存款线程 new Withdraw("取款者甲",ac,325.0).start(); //开启两个取款线程 new Deposit("存款者B",ac,325.0).start(); new Withdraw("取款者乙",ac,325.0).start(); } } class Withdraw extends Thread{ //取款任务 private Account account; private double withdrawAmount; public Withdraw (String threadName,Account account,double withdrawAmount){ super(threadName); this.account=account; this.withdrawAmount=withdrawAmount; } public void run(){ for (int i=1;i<=2;i++){ //每个线程循环取款2次 account.withdraw(withdrawAmount); } } } class Deposit extends Thread{ //存款任务 private Account account; private double depositAmount; public Deposit (String threadName,Account account,double depositAmount){ super(threadName); this.account=account; this.depositAmount=depositAmount; } public void run(){ for (int i=1;i<=2;i++){ //每个线程循环存款2次 account.deposit(depositAmount); } } } class Account { private String accountNO; private double balance; //账户余额 private boolean flag=false; //用于判断该账户是否可以进行存款或取款 public Account(){} public Account(String no,double balance){ accountNO=no; this.balance=balance; } public double getBalance(){ return balance; } public synchronized void withdraw(double amount){ //同步方法,取款 try { while (!flag){ //标记㈠。特别注意,这里用while进行循环判断,而不是用if-else判断 this.wait(); //flag为false,则不可取款,线程等待,并释放同步锁 } System.out.println(Thread.currentThread().getName()+"取款:"+amount); balance-=amount; System.out.println("取款后,余额为: "+balance); flag=false; //取款完毕后,将flag切换为false,下一个线程如果是取款线程,则不能取款 System.out.println("---------------上面取款完毕-------------------"); this.notifyAll(); //标记㈢。取款完毕,唤醒其他所有线程 }catch(InterruptedException ex){ ex.printStackTrace(); } } public synchronized void deposit(double amount){ //同步方法,存款 try{ while (flag){ //标记㈡。特别注意,这里用while进行循环判断,而不是用if-else判断 this.wait(); //如果flag为true,则不能存款,线程等待并释放同步锁 } System.out.println(Thread.currentThread().getName()+"存款"+amount); balance+=amount; System.out.println("存款后,账户余额为: "+balance); flag=true; //存款完毕后,将flag切换为true,下一个线程如果是存款线程,则不能存款 System.out.println("---------------上面存款完毕-------------------"); this.notifyAll(); //标记㈣存款完毕后,唤醒其他所有线程 }catch(InterruptedException ex){ ex.printStackTrace(); } } }

输出:

存款者A存款325.0
存款后,账户余额为: 325.0
---------------上面存款完毕-------------------
取款者乙取款:325.0
取款后,余额为: 0.0
---------------上面取款完毕-------------------
存款者B存款325.0
存款后,账户余额为: 325.0
---------------上面存款完毕-------------------
取款者甲取款:325.0
取款后,余额为: 0.0
---------------上面取款完毕-------------------
存款者B存款325.0
存款后,账户余额为: 325.0
---------------上面存款完毕-------------------
取款者乙取款:325.0
取款后,余额为: 0.0
---------------上面取款完毕-------------------
存款者A存款325.0
存款后,账户余额为: 325.0
---------------上面存款完毕-------------------
取款者甲取款:325.0
取款后,余额为: 0.0
---------------上面取款完毕-------------------

看上面的输出:存款者A和B,取款者甲和乙分别各进行了2次存款或取款操作,并且交替执行

看上面的标记㈢和㈣

这里只能使用notifyAll(),而不能使用notify()方法,因为可能导致程序阻塞,比如:

存款A线程第一次存款完毕,唤醒一个线程(当然第一次没有线程可供唤醒)并再次执行,wait()。状态:A阻塞+B甲乙就绪

存款B线程试图存款,失败,wait()。状态:AB+甲乙

取款甲线程第一次取款完毕,唤醒存款A线程,并再次执行,wait()。状态:B甲+A乙

取款乙线程试图取款,失败,wait()。状态:B甲乙+A

存款A线程第二次存款完毕,唤醒存款B线程,并再次执行,wait()。状态:甲乙A+B

存款B线程试图存款,失败,wait()。状态:AB甲乙均处于wait()状态

此时,四个线程都处于阻塞状态

再看上面的标记㈠和㈡

上面这段代码主要来源于《疯狂Java讲义 第三版》的“codes\16\16.6\synchronized”目录

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zzzgpj.html