假设这样一个情景:在银行的营业厅内先后进来3个人,他们都要进行存款,若是只有一个营业窗口的话,通常的情况是每人都需要先领取顺序条,然后按序排队办理业务,而营业厅会根据号码的顺序依次叫号来处理顾客的问题。
在这里银行的窗口就可以看做共享的资源,它每次只能接待一个顾客,而不同的顾客则可以看做是多个线程,他们都需要办理业务,但是又必须遵守先来后到的原则,排队等待前面的顾客办理完业务才能轮到自己独占窗口办理自己的业务(当然也可能存在插队现象,后面会讲到)。
为了简化银行处理业务的过程,我们假设每个客户只是办理简单的存款业务,银行窗口只需清点出存款数额就代表业务处理完成。
(1)首先我们定义一个银行类Bank,用它来模拟银行。为了能够让整个过程可视化,我们让这个类继承自Applet。而这个银行的主业务–存款(其实就是点钱),我们用方法countMoney来模拟,由于这是个临界资源,所以我们要给他加上互斥锁synchronize,以保证每次只有一个顾客能够使用这个功能。
public class Bank extends Applet{ ......
public synchronized void countMoney(String name, int total){ int count = 1; while(count <= total){ t.setText("(" + name + ") " + Integer.toString(count++)); } notifyAll(); }
...... }
为了能够看清楚这个排队等待数钱的过程,我们需要两个文本区域,一个用来显示当前正在办理业务的客户数钱的过程,另一个显示等待数钱的客户队列。
public TextField counting= new TextField(10); public TextField deque = new TextField(100);
另外我们给该银行模拟了三个用户,他们分别是马云、雷军和马化腾–。
private MaYun maYun = null; private LeiJun leiJun = null; private MaHuaTeng maHuaTeng = null;
整个Bank类如下所示,其中方法setDeque表示向排队队列中加入顾客。
public class Bank extends Applet{ public TextField counting = new TextField(10); public TextField deque = new TextField(100); private MaYun maYun = null; private LeiJun leiJun = null; private MaHuaTeng maHuaTeng = null; public void init() { add(counting); add(deque); maYun = new MaYun(Bank.this); leiJun = new LeiJun(Bank.this); maHuaTeng = new MaHuaTeng(Bank.this); } public void setDequeue(String person){ String list = deque.getText(); list = list + person + "->"; deque.setText(list); } public synchronized void countMoney(String name, int total){ int count = 1; while(count <= total){ counting.setText("(" + name + ") " + Integer.toString(count++)); } notifyAll(); } public static void main(int argc, String args[]){ Bank applet = new Bank(); Frame aFrame = new Frame("Counter2"); aFrame.add(applet, BorderLayout.CENTER); aFrame.setSize(300, 200); applet.init(); applet.start(); aFrame.setVisible(true); } }
View Code
(2)定义三个线程类,在每个线程类的run方法中都调用银行类中的countMoney方法。由于countMoney加了互斥锁,所以每次只有一个线程可以执行这个方法,而其他的线程都处于阻塞状态。一旦一个县城执行完了countMoney方法,他就会调用notifyAll方法,唤醒其他阻塞的线程。这个过程就模仿了顾客在营业厅排队办理业务的场景。
public class MaYun extends Thread { private static int money = 10000; private static String name = "马云"; Bank c = null; Button start = new Button(name); public MaYun(Bank c){ this.c = c; start.addActionListener(new StartListener()); c.add(start); } public void run() { c.setDequeue(name); c.countMoney(name, money); } public class StartListener implements ActionListener{ public void actionPerformed(ActionEvent e) { MaYun.this.start(); } }; }
View Code
public class LeiJun extends Thread { private static int money = 8000; private static String name = "雷军"; Bank c = null; Button start = new Button(name); public LeiJun(Bank c){ this.c = c; start.addActionListener(new StartListener()); c.add(start); } public void run() { c.setDequeue(name); c.countMoney(name, money); } public class StartListener implements ActionListener{ public void actionPerformed(ActionEvent e) { LeiJun.this.start(); } }; }
View Code
public class MaHuaTeng extends Thread { private static int money = 20000; private static String name = "马化腾"; Bank c = null; Button start = new Button(name); public MaHuaTeng(Bank c){ this.c = c; start.addActionListener(new StartListener()); c.add(start); } public void run() { c.setDequeue(name); c.countMoney(name, money); } public class StartListener implements ActionListener{ public void actionPerformed(ActionEvent e) { MaHuaTeng.this.start(); } }; }
View Code
(3)下面给出实验过程中运行的界面结果。我们依次点击马云、雷军、马化腾三个按钮,表示这3个人依次来银行办理业务。由于马云是最先来到的,所以他可以立即到窗口办理点钱业务。而由于处理业务的窗口只有一个,雷军和马化腾只能等马云结束,他们才能办理自己的业务。但是在实际的运行过程中,我们发现马云办理完业务后,紧接着办理业务的不是雷军而是马化腾。这主要是因为当马云办理完业务后,雷军和马化腾并没有按照先来后到的排队顺序来,马化腾插队了。这说明使用notifyAll方法,所有阻塞的线程在获得临近资源的机会上是均等的,谁先抢到,谁先用。