1.使用Object类的方法来实现线程之间的通信
为了实现线程通信,我们可以使用Object类提供的wait()、notify()、notifyAll()三个方法。调用wait()方法会释放对该同步监视器的锁定。这三个方法必须由同步监视器对象来调用,这可分成两种情况:
- 对于使用synchronized修饰的同步方法,因为该类的默认实例是(this)就是同步监视器,所以可以直接调用这三使用个方法。
- 对于synchronized修饰的同步代码块,同步监视器是synchronized括号里的对象,所以必须使用该对象调用这三个方法。
这次我们还是拿银行账户的取钱、存款来作为本文的例子演示。
假设系统中有两条线程,这两条线程分别代表取钱者和存钱者。现在系统有一种特殊的要求,系统要求存款者和取钱者不断的实现存款和取钱动作,而且要求每当存款者将钱存入指定账户后,取钱者立即将钱取走.不允许存款者两次存钱,也不允许取钱者两次取钱.
我们通过设置一个旗标来标识账户中是否已有存款,有就为true,没有就标为false。具体代码如下:
首先我们定义一个Account类,这个类中有取钱和存钱的两个方法,由于这两个方法可能需要并发的执行取钱、存钱操作,所有将这两个方法都修改为同步方法.(使用synchronized关键字)。
public class Account { private String accountNo; private double balance; //标识账户中是否有存款的旗标 private boolean flag=false; public Account() { super(); } public Account(String accountNo, double balance) { super(); this.accountNo = accountNo; this.balance = balance; } public synchronized void draw (double drawAmount){ try { if(!flag){ this.wait(); }else { //取钱 System.out.println(Thread.currentThread().getName()+" 取钱:"+drawAmount); balance=balance-drawAmount; System.out.println("余额 : "+balance); //将标识账户是否已有存款的标志设为false flag=false; //唤醒其它线程 this.notifyAll(); } } catch (Exception e) { e.printStackTrace(); } } public synchronized void deposit(double depositAmount){ try { if(flag){ this.wait(); } else{ System.out.println(Thread.currentThread().getName()+"存钱"+depositAmount); balance=balance+depositAmount; System.out.println("账户余额为:"+balance); flag=true; //唤醒其它线程 this.notifyAll(); } } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } } }
接下来创建两个线程类,分别为取钱和存钱线程!
取钱线程类:
public class DrawThread implements Runnable { private Account account; private double drawAmount; public DrawThread(Account account, double drawAmount) { super(); this.account = account; this.drawAmount = drawAmount; } public void run() { for(int i=0;i<100;i++){ account.draw(drawAmount); } } }
存钱线程类:
public class depositThread implements Runnable{ private Account account; private double depositAmount; public depositThread(Account account, double depositAmount) { super(); this.account = account; this.depositAmount = depositAmount; } public void run() { for(int i=0;i<100;i++){ account.deposit(depositAmount); } } }
最后我们测试一下这个取钱和存钱的操作!
public class TestDraw { public static void main(String[] args) { //创建一个账户 Account account=new Account(); new Thread(new DrawThread(account, 800),"取钱者").start(); new Thread(new depositThread(account, 800),"存款者甲").start(); new Thread(new depositThread(account, 800),"存款者乙").start(); new Thread(new depositThread(account, 800),"存款者丙").start(); } }
大致的输出结果:
存款者甲存钱800.0 账户余额为:800.0 取钱者 取钱:800.0 余额 : 0.0 存款者丙存钱800.0 账户余额为:800.0 取钱者 取钱:800.0 余额 : 0.0 存款者甲存钱800.0 账户余额为:800.0 取钱者 取钱:800.0 余额 : 0.0 存款者丙存钱800.0 账户余额为:800.0 取钱者 取钱:800.0 余额 : 0.0 存款者甲存钱800.0 账户余额为:800.0 取钱者 取钱:800.0 余额 : 0.0 存款者丙存钱800.0 账户余额为:800.0 取钱者 取钱:800.0 余额 : 0.0 存款者甲存钱800.0 账户余额为:800.0 取钱者 取钱:800.0 余额 : 0.0 存款者丙存钱800.0 账户余额为:800.0 取钱者 取钱:800.0 余额 : 0.0 存款者甲存钱800.0 账户余额为:800.0 取钱者 取钱:800.0 余额 : 0.0
由上述结果我们可以看到,存款者线程和取钱者线程交替执行的情形。至此简单的线程通信问题已经模拟完毕!
2.使用java.util.concurrent.locks下的Condition类来实现线程间的通信
如何程序不使用synchronized关键字来保持同步,而是直接适用Lock对像来保持同步,则系统中不存在隐式的同步监视器对象,也就不能使用wait()、notify()、notifyAll()来协调线程的运行.
当使用LOCK对象保持同步时,JAVA为我们提供了Condition类来协调线程的运行。关于Condition类,JDK文档里进行了详细的解释.,再次就不啰嗦了。
我们就拿Account类进行稍微的修改 一下吧!
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Account { //显示定义Lock对象 private final Lock lock=new ReentrantLock(); //获得指定Lock对象对应的条件变量 private final Condition con=lock.newCondition(); private String accountNo; private double balance; //标识账户中是否有存款的旗标 private boolean flag=false; public Account() { super(); } public Account(String accountNo, double balance) { super(); this.accountNo = accountNo; this.balance = balance; } public void draw (double drawAmount){ //加锁 lock.lock(); try { if(!flag){ // this.wait(); con.await(); }else { //取钱 System.out.println(Thread.currentThread().getName()+" 取钱:"+drawAmount); balance=balance-drawAmount; System.out.println("余额 : "+balance); //将标识账户是否已有存款的标志设为false flag=false; //唤醒其它线程 // this.notifyAll(); con.signalAll(); } } catch (Exception e) { e.printStackTrace(); } finally{ lock.unlock(); } } public void deposit(double depositAmount){ //加锁 lock.lock(); try { if(flag){ // this.wait(); con.await(); } else{ System.out.println(Thread.currentThread().getName()+"存钱"+depositAmount); balance=balance+depositAmount; System.out.println("账户余额为:"+balance); flag=true; //唤醒其它线程 // this.notifyAll(); con.signalAll(); } } catch (Exception e) { // TODO: handle exception e.printStackTrace(); }finally{ lock.unlock(); } } }
输出结果和上面是一样的! 只不过这里 显示的使用Lock对像来充当同步监视器,使用Condition对象来暂停指定线程,唤醒指定线程!