多線程一個很有意思的作用就是用於仿真,這篇博客就會結合幾個仿真實例來綜合運用一下前面所學的多線程並發知識。
一.銀行出納員仿真
問題描述:銀行會有很多來辦業務的顧客,他們會排隊等待服務;對於銀行方面他們派出出納員來服務顧客,如果排隊的顧客數量過多,銀行就會增加
出納員的數量,如果顧客的數目過少,則減少出納員的數目;總之要保持一個平衡。
仿真思路:封裝Customer類來表示顧客,每個顧客對象都會有一個需要服務的時間;使用有限容量的阻塞隊列CustomerLine來模擬顧客的排隊隊列;封裝
CustomerGenerator類來產生顧客,然後將產生的顧客加入到CustomerLine中去;封裝Teller類來表示銀行的出納員,Teller會從CustomerLine中取出;
Customer來進行服務。封裝TellerManage來管理所有的Teller及根據顧客/出納員的比例來調整服務顧客的Teller數量。在這裡我們通過阻塞隊列CustomerLine實現了Teller線程和CustomerGenerator線程之間的通信。
具體的實現代碼如下:
package lkl;
import java.util.LinkedList;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Random;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* 多線程模擬銀行出納員問題
* */
//模擬顧客類,完全只是一個可讀類,不需要同步
class Customer{
//該顧客所需服務時間
private final int serviceTime;
public Customer(final int serviceTime){
this.serviceTime = serviceTime;
}
public int getServiceTime(){
return serviceTime;
}
public String toString(){
return "["+serviceTime+"]";
}
}
//模擬顧客排隊的隊列,繼承了阻塞隊列
//是一個多線程共享對象,這個隊列繼承的是ArrayBlocingQueue
//是一個有最大長度的隊列
class CustomerLine extends ArrayBlockingQueue{
//指定允許隊列的最大長度
public CustomerLine(int maxSize){
super(maxSize);
}
//重寫toString()方法,用來進行顯示當前排隊中的顧客
public String toString(){
if(this.size()==0)
return "[Empty]";
StringBuilder result = new StringBuilder();
for(Customer customer :this){
result.append(customer);
}
return result.toString();
}
}
//顧客生產類
//間隔隨機然後向隊列中添加一位顧客的線程
class CustomerGenerator implements Runnable{
private CustomerLine customerLine; //阻塞隊列
private static Random rand = new Random(47);
public CustomerGenerator(CustomerLine customerLine){
this.customerLine = customerLine;
}
public void run(){
try{
while(!Thread.interrupted()){
//線程睡眠隨機時間以後,產生一個顧客對象,添加到隊列中
TimeUnit.MILLISECONDS.sleep(rand.nextInt(300));
//添加一個服務時間隨機的顧客
customerLine.add(new Customer(rand.nextInt(1000)));
}
}catch(InterruptedException ex){
System.out.println(this+" 通過中斷異常退出");
}
System.out.println(this+" terminating");
}
}
//出納員類,負責對隊列中的顧客進行服務
//注意其有兩種狀態:服務顧客或做一些其它的事情
class Teller implements Runnable,Comparable{
private static int counter = 0;
private final int id = counter++;
//該Teller服務的顧客隊列
private CustomerLine customerLine;
private int customerServed = 0;//已服務的顧客數
//標志目前是被分配到服務CustomerLine還是做一些其它事
//默認是分配給customerLine
private boolean servingCustomerLine=true;
public Teller(CustomerLine cl){
this.customerLine = cl;
}
//正常情況下會從CustomerLine中取出一個Customer進行服務
//如果被分配到做其它事,則會被掛起
public void run(){
try{
while(!Thread.interrupted()){
Customer customer = customerLine.take();
//睡眠一段時間模擬服務Customer
TimeUnit.MILLISECONDS.sleep(customer.getServiceTime());
synchronized(this){
while(!servingCustomerLine){//被分配做其它事情
wait();
}
}
}
}catch(InterruptedException ex){
System.out.println(this+"通過中斷異常退出");
}
System.out.println(this+"Terminating");
}
//調用這個方法意味著該Teller對象被分配去做其它事情
public synchronized void doSomethingElse(){
customerServed = 0;
servingCustomerLine=false; //設定標志,是當前服務線程掛起
}
//被分配到服務到customerLine
public synchronized void serveCustomerLine(){
servingCustomerLine = true;
notifyAll();//通知掛起線程
}
public String toString(){
return "Teller "+id+" ";
}
public String shortString(){
return "T "+id;
}
//按以服務顧客數確定Teller的優先級,給優先隊列使用
@Override
public synchronized int compareTo(Teller other){
return customerServed < other.customerServed ? -1:
(customerServed==other.customerServed ? 0 :1);
}
}
//服務管理和調度Teller的類
//這個TellerManager類是各種活動的中心,它跟蹤所有的出納員以及等待服務的顧客
//從adjustTellerNumber()中可以看到,它會根據實際情況調整服務CustomerLine的
//Teller數量,以期達到最優出納員的數目。
class TellerManager implements Runnable{
private ExecutorService exec; //負責啟動Teller線程
private CustomerLine customerLine;
//按服務顧客數由少到多優先的優先隊列,用來進行調度
//每次都取出服務顧客數最少的出納員來進行服務,以保證公平性。
private PriorityQueue workingTellers
= new PriorityQueue<>();
//正在做其它事情的Teller隊列
private Queue tellersDoingOtherThings
= new LinkedList();
private int adjustmentPeriod; //調度時間
private static Random rand = new Random();
public TellerManager(ExecutorService exec,CustomerLine
customerLine,int adjustmentPeriod){
this.exec =exec;
this.customerLine = customerLine;
this.adjustmentPeriod = adjustmentPeriod;
//在構造器中先分配一個Teller進行服務
Teller teller = new Teller(customerLine);
exec.execute(teller);
workingTellers.add(teller);
}
//通過當前customerLine中的顧客數以及正在工作的Teller
//人數的比例關系,來確定是否要加/減Teller的數目
public void adjustTellerNumber(){
//如果customerLine隊列過長,則增加服務的Teller
if(customerLine.size()/workingTellers.size()>2){
//如果在做其它事的Teller則從中抽調出人來,否則重新分配一個Teller
if(tellersDoingOtherThings.size()>0){
Teller teller = tellersDoingOtherThings.remove();
teller.serveCustomerLine();
workingTellers.add(teller);
return;
}
//重新分配一個Teller
Teller teller = new Teller(customerLine);
exec.execute(teller);
workingTellers.add(teller);
return;
}
//當前Tellers過多時,抽調一些去做其它工作
if(workingTellers.size()>1&&customerLine.size()/workingTellers.size()<2){
reassignOneTeller();
//如果這裡只有沒有customer需要服務,則只需留下一個Teller
if(customerLine.size()==0){
while(workingTellers.size()>1){
reassignOneTeller();
}
}
}
}
private void reassignOneTeller() {
//從工作隊列中取出一個Teller來
Teller teller = workingTellers.poll();
teller.doSomethingElse();//讓他去做其它工作
tellersDoingOtherThings.offer(teller);
}
public void run(){
try{
while(!Thread.interrupted()){
TimeUnit.MILLISECONDS.sleep(adjustmentPeriod);
//按當前情況進行動態調整
adjustTellerNumber();
//打印當前的customerLine和workingTeller的情況
//從結果可以看到隨著customerLine大小的變化,workingTeller
//的人數也是不斷變化的。
System.out.print(customerLine+"{");
for(Teller teller: workingTellers){
System.out.print(teller.shortString()+" ");
}
System.out.println("}");
}
}catch(InterruptedException ex){
System.out.println(this+"通過中斷異常退出");
}
System.out.println(this+"terminating");
}
public String toString(){
return "TellerManager";
}
}
public class BankTellerSimulation {
static final int SIZE = 50;//顧客隊列的最大長度
static final int PERIOD = 1000;//調整時間間隔
public static void main(String[] args) throws Exception{
ExecutorService exec = Executors.newCachedThreadPool();
CustomerLine customerLine = new CustomerLine(SIZE);
exec.execute(new CustomerGenerator(customerLine));
exec.execute(new TellerManager(exec,customerLine,PERIOD));
System.out.println("Press 'Enter' to exit");
System.in.read();
exec.shutdownNow();
}
}
二.飯店仿真
問題描述:模擬飯店的場景:飯店中有顧客到來以後就會派一個侍者進行服務,然後侍者記錄顧客所點的食物以後就提交訂單到飯店,然後飯店的廚師取的訂單
以後就做好食物然後再由相應的侍者交給顧客。
仿真思路:封裝Oder類表示用戶的訂單,訂單中包含了點餐的顧客,對應的侍者和顧客所點的食物;封裝Plate類表示裝有廚師做好訂單上食物的盤子;封裝Customer類
表示顧客,每個顧客會隨機選擇一種食物然後由服務該顧客的侍者提交訂單給飯店,當食物做好以後,顧客吃掉完成消費過程;封裝WaitPerson類表示侍者,侍者一方面幫助服務的顧客提交訂單,另一方面將飯店廚師做好的食物交給對應的顧客;封裝Chef表示飯店的廚師,廚師從飯店中取得侍者提交的訂單,然後做完其中的食物,然後將對應的Plate提交給該訂單對應的WaitPerson;封裝Restaurant類表示飯店,飯店中有廚師隊列,侍者隊列,訂單隊列,飯店進程中還會每隔一段時間生成一個顧客。
值得注意的是這裡其實牽涉到了多個線程之間協調,但是這些並不是通過直接的線程之間的通信來實現的而是通過阻塞隊列來實現的;比如說顧客點了食物以後,侍者會提交一份訂單,但是這份訂單不是給廚師的,而是提交給飯店的訂單阻塞隊列,然後廚師從這個訂單隊列中取出訂單制作好食物以後並不需要直接通知侍者,而是會提交給侍者的阻塞隊列,然後侍者再從它的阻塞隊列中取出食物來提交給顧客的阻塞隊列,然後顧客在合適的時間從其隊列中取出食物來食用。從上面的過程中可以看到使用隊列極大地降低了線程間通信的復雜度:任務之間沒有直接的相互干涉,而是經由隊列來相互發送對象。接收任務將處理對象,將其當成一個消息來對待,而不是向它發送消息。
具體實現代碼如下:
package lkl;
import java.util.ArrayList;
import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
class Course{
private static Random rand = new Random();
public static String[] food={"food1","food2","food3","food4"};
public static String randomSelection(){
return food[rand.nextInt(food.length)];
}
}
//封裝的訂單類
class Order{
private static int counter=0;
private final int id = counter++; //訂單唯一的編號
private final Customer customer; //訂單對應的顧客
private final WaitPerson waitPerson; //負責該訂單的服務員
private final String food; //訂單對應的食物
public Order(Customer cust,WaitPerson wait,String food){
this.customer = cust;
this.waitPerson = wait;
this.food = food;
}
//返回訂單中的食物
public String item(){
return food;
}
public Customer getCustomer(){
return customer;
}
public WaitPerson getWaitPerson(){
return waitPerson;
}
public String toString(){
return "Order: "+id+"item: "+food+" for: "+customer+" served by: "+waitPerson;
}
}
//裝好食物的碟子類
class Plate{
private final Order order; //該碟子對應的訂單
private final String food; //該碟子盛放的食物
public Plate(Order order , String food){
this.order = order;
this.food = food;
}
public Order getOrder(){
return order;
}
public String getFood(){
return food;
}
public String toString(){
return food;
}
}
//顧客類
class Customer implements Runnable{
private static int counter = 0;
private final int id = counter++; //顧客id
private final WaitPerson waitPerson ;//服務該顧客的侍者
//表示顧客面前的盤子,在我們的仿真中顧客只會消費一種食物,所以我們使用了
//容量為1的阻塞隊列SynchronousQueue來表示其前面的盤子,這個隊列每個put()操作
//後面都必須跟一個take()操作,否則就會阻塞。
private SynchronousQueue placeSetting = new SynchronousQueue();
public Customer(WaitPerson wait){
this.waitPerson = wait;
}
//將制作完成的食物提交給顧客,如果前面已經put()過並且
//用戶還沒有take()則會阻塞
public void deliver(Plate p) throws InterruptedException{
placeSetting.put(p);
}
public void run(){
for(String food: Course.food){
//每次用戶都會從菜單中隨機選擇一種食物
food =Course.randomSelection();
try{
//waitPerson提交用戶的訂單
waitPerson.placeOrder(this,food);
//表示用戶吃掉食物,如果食物還沒做好,則take()操作會阻塞
System.out.println(this+" eating "+placeSetting.take());
}catch(InterruptedException ex){
System.out.println("Interrupted");
break;
}
}
System.out.println(this+"finished meal,leaving");
}
public String toString(){
return "Customer "+id+" ";
}
}
//封裝的侍者類
class WaitPerson implements Runnable{
private static int counter = 0;
private final int id = counter++; //侍者編號
private final Restaurant restaurant;//侍者所屬的飯店
//無界的阻塞隊列,用來存放廚師已經完成的食物
//侍者需要將這些食物送到對應的顧客手上
LinkedBlockingQueue filledOrders = new LinkedBlockingQueue();
public WaitPerson(Restaurant rest){
this.restaurant = rest;
}
//當用戶點了食物以後,侍者提交訂單
public void placeOrder(Customer cust, String food){
try{
//向餐館的訂單隊列中提交一個新訂單
restaurant.orders.put(new Order(cust,this,food));
}catch(InterruptedException ex){
System.out.println("Intrrupted");
}
}
//侍者線程的主要作用是不斷的從filledOrders中取出已完成的食物
//提交給對應的顧客
public void run(){
try{
while(!Thread.interrupted()){
//如果隊列為空,則會阻塞
Plate plate = filledOrders.take();
System.out.println(this+"received "+plate+" delivering to "+plate.getOrder().getCustomer());
//將提取的plate提交給對應的顧客
plate.getOrder().getCustomer().deliver(plate);
}
}catch(InterruptedException ex){
System.out.println(this +"Interrupted");
}
}
public String toString(){
return "waitPerson "+id+" ";
}
}
//廚師類
class Chef implements Runnable{
private static int counter = 0;
private final int id = counter++;//廚師編號
private final Restaurant restaurant ;//廚師對應的餐館
private Random rand = new Random(47);
public Chef(Restaurant rest){
restaurant = rest;
}
//廚師線程的主要任務是從飯店的訂單隊列提取訂單,然後完成其中的食物
//再將完成以後的plate提交給對應的侍者的filledOrders隊列
public void run(){
try{
while(!Thread.interrupted()){
//從訂單隊列中取出訂單,如果沒有訂單則會阻塞
Order order = restaurant.orders.take();
String food = order.item();//取得該訂單所需的食物
//模擬准備這種食物所需的時間
TimeUnit.MILLISECONDS.sleep(rand.nextInt(500));
Plate plate = new Plate(order,food);
//將完成的plate交給對應的waitPerson
order.getWaitPerson().filledOrders.put(plate);
}
}catch(InterruptedException ex){
System.out.println(this+"Interrupted");
}
System.out.println(this +"off duty");
}
public String toString(){
return "Chef "+id+" ";
}
}
//飯店類
class Restaurant implements Runnable{
//飯店的侍者隊列
private ArrayList waitPersons = new ArrayList();
//飯店的廚師隊列
private ArrayList chefs = new ArrayList();
private ExecutorService exec = Executors.newCachedThreadPool();
private static Random rand = new Random(47);
//飯店的訂單隊列
BlockingQueue orders = new LinkedBlockingQueue();
public Restaurant(ExecutorService exe,int nWaitPerson,int nChef){
exec = exe;
//預先為飯店分配好侍者和廚師
for(int i=0;i
三.汽車裝配工廠仿真
問題描述:模擬一條汽車生產線;汽車的生產過程首先是生產底盤,然後在底盤上裝配好發動機,動力傳動系統,車輪,然後一輛車就生產完成啦。
仿真思路:封裝Car類表示汽車,這個類裡同時包含了構建汽車的幾個方法;封裝ChassisBuilder類表示建造底盤的類;封裝Assembler類表示組合其它部分的類,這個類
負責調用不同的機器人來組裝汽車不同的部分;封裝Robot類表示抽象的機器人,每個機器人都會屬於一個RobotPool,同時會關聯到一個Assembler(組裝工作),當工作完成以後這個聯系就會被取消掉;同時還會繼承Robot實現具體的機器人類。封裝RobotPool類來管理所有的Robot,Assember需要機器人則從中調用。更具體的思路見下面的代碼。
具體實現代碼如下:
package lkl;
import java.util.HashSet;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
//封裝Car類表示汽車
class Car1{
private final int id;//汽車編號
//表示開始時汽車各部分都還沒組裝好
private boolean engine = false ,driveTrain = false, wheels = false;
public Car1(int id){
this.id = id;
}
public Car1(){
id = -1;
}
public synchronized int getId(){
return id;
}
//以下是組裝汽車的步驟
//這裡通過設定指定的標記為true,表示完成了相應的步驟
public synchronized void addEngine(){
engine = true;
}
public synchronized void addDriveTrain(){
driveTrain = true;
}
public synchronized void addWheels(){
wheels = true;
}
public synchronized String toString(){
return "Car "+id+" ["+" engine: "+engine+" driveTrain: "+driveTrain+" wheels: "+wheels+" ]";
}
}
//封裝的汽車隊列,是一個阻塞隊列
class CarQueue extends LinkedBlockingQueue{};
//建造底盤的類
//建好底盤以後就將放入相應的阻塞隊列中,供後面的線程使用
class ChassisBuilder implements Runnable{
private CarQueue carQueue; //存放建好底盤的汽車
private int counter = 0;
public ChassisBuilder(CarQueue queue){
carQueue = queue;
}
//線程的主要任務就是生成汽車底盤,放入阻塞隊列中
public void run(){
try{
while(!Thread.interrupted()){
TimeUnit.MILLISECONDS.sleep(400);
Car1 c = new Car1(counter++);
System.out.println("ChassisBuilder created "+c);
carQueue.put(c);
}
}catch(InterruptedException ex){
System.out.println("ChassisBuilder interrpted");
}
System.out.println("ChassisBuilder off");
}
}
//組裝類,通過調用機器人在建好的底盤上組裝其它部分
class Assembler implements Runnable{
//分配記錄裝好底盤的Car和已經完成組裝號的Car
private CarQueue chassisQueue,finishedQueue;
private Car1 car; //正在組裝的Car
private CyclicBarrier barrier = new CyclicBarrier(4);
private RobotPool robotPool;
public Assembler(CarQueue cq,CarQueue fq,RobotPool rt){
chassisQueue = cq;
finishedQueue = fq;
robotPool = rt;
}
public Car1 getCar(){
return car;
}
public CyclicBarrier getBarrier(){
return barrier;
}
//線程的主要任務就是負責調用機器人來組裝Car
//注意這裡使用了CyclicBarrier來一輛車完成裝好以後才能繼續組裝下一輛
public void run(){
try{
while(!Thread.interrupted()){
//如果底盤還沒有生成則會阻塞
car = chassisQueue.take();
//下面會雇傭各個類型的robot去組裝這輛汽車
robotPool.hire(EngineRobot.class,this);
// System.out.println("test");
robotPool.hire(DriveTrainRobot.class,this);
robotPool.hire(WheelsRobot.class,this);
barrier.await(); //如果上面的組裝還沒完成,則會阻塞在這裡;這樣可以保證一輛車組裝完以後再組裝下一輛車
finishedQueue.put(car); //將組裝完成的車加入隊列
}
}catch(Exception ex){
System.out.println("Assemble Interrupted");
}
System.out.println("Assemble off");
}
}
//將組裝好的汽車輸出進行檢查
class Reporter implements Runnable{
private CarQueue carQueue;
public Reporter(CarQueue carQueue){
this.carQueue = carQueue;
}
//線程的主要任務是將組裝完成的汽車打印出來
public void run(){
try{
while(!Thread.interrupted()){
System.out.println(carQueue.take());
}
}catch(InterruptedException ex){
System.out.println("reporter interrupted");
}
}
}
//負責組裝工作的機器人類,是一個抽象類
//下面會有各種機器人的具體實現
abstract class Robot implements Runnable{
private RobotPool robotPool;
public Robot(RobotPool pool){
robotPool = pool;
robotPool.add(this); //將自己加入管理池中去
//robotPool.pool.add(this);
}
protected Assembler assembler; //該機器人服務的組裝線
//關聯到指定的組裝線
public Robot assignAssembler(Assembler am){
assembler = am;
return this;
}
private boolean engage = false; //是否在干活
//讓機器人干活
public synchronized void engage(){
engage = true;
notifyAll();
}
//由子類實現的抽象方法,每個子類的行為都不一樣
abstract protected void performService();
public void run(){
try{
powerDown(); //如果沒有組裝線雇傭這個機器人,則線程在此阻塞
while(!Thread.interrupted()){
performService();//干活
assembler.getBarrier().await(); //表示自己的活已經干完
powerDown();
}
}catch(Exception ex){
System.out.println("Exception");
}
}
private synchronized void powerDown() throws Exception{
engage = false;
assembler = null ;//解除和裝配線的聯系
robotPool.release(this);
while(engage==false){//沒有活干時掛起
wait();
}
}
public String toString(){
return getClass().getName();
}
}
//裝配發動機的機器人
class EngineRobot extends Robot{
public EngineRobot(RobotPool pool){
super(pool);
}
protected void performService(){
System.out.println(this+" installing engine");
assembler.getCar().addEngine();
}
}
//裝配傳動系統的機器人
class DriveTrainRobot extends Robot{
public DriveTrainRobot(RobotPool pool){
super(pool);
}
protected void performService(){
System.out.println(this+" installing driveTrain");
assembler.getCar().addDriveTrain();;
}
}
//裝配輪子的機器人
class WheelsRobot extends Robot{
public WheelsRobot(RobotPool pool){
super(pool);
}
protected void performService(){
System.out.println(this+" installing Wheels");
assembler.getCar().addWheels();
}
}
//集中管理所有的機器人
class RobotPool{
public HashSet pool = new HashSet<>();
public synchronized void add(Robot r){
pool.add(r);
notifyAll();
}
public synchronized void hire(ClassrobotType,Assembler d) throws Exception{
for(Robot r: pool){//找到合適品種的機器人,如果找不到則等待再遞歸尋找
if(r.getClass().equals(robotType)){
pool.remove(r);
r.assignAssembler(d);//關聯生產線
r.engage();//讓機器人干活
return ;
}
}
wait();//當前沒有多余的機器人則等待直到有空閒的再遞歸搜索
hire(robotType,d);//遞歸
}
public synchronized void release(Robot r){
add(r);
}
}
public class CarBuilder {
public static void main(String[] args) throws Exception{
CarQueue chassisQueue = new CarQueue(),
finishedQueue = new CarQueue();
ExecutorService exec = Executors.newCachedThreadPool();
//依次啟動各個機器人,生產線
RobotPool robotPool = new RobotPool();
exec.execute(new EngineRobot(robotPool));
exec.execute(new DriveTrainRobot(robotPool));
exec.execute(new WheelsRobot(robotPool));
exec.execute(new Assembler(chassisQueue,finishedQueue,robotPool));
exec.execute(new Reporter(finishedQueue));
exec.execute(new ChassisBuilder(chassisQueue));
TimeUnit.SECONDS.sleep(7);
exec.shutdownNow();
}
}