多线程一个很有意思的作用就是用于仿真,这篇博客就会结合几个仿真实例来综合运用一下前面所学的多线程并发知识。
一.银行出纳员仿真
问题描述:银行会有很多来办业务的顾客,他们会排队等待服务;对于银行方面他们派出出纳员来服务顾客,如果排队的顾客数量过多,银行就会增加
出纳员的数量,如果顾客的数目过少,则减少出纳员的数目;总之要保持一个平衡。
仿真思路:封装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<Customer>{
//指定允许队列的最大长度
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<Teller>{
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<Teller> workingTellers
= new PriorityQueue<>();
//正在做其它事情的Teller队列
private Queue<Teller> tellersDoingOtherThings
= new LinkedList<Teller>();
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<Plate> placeSetting = new SynchronousQueue<Plate>();
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<Plate> filledOrders = new LinkedBlockingQueue<Plate>();
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<WaitPerson> waitPersons = new ArrayList<WaitPerson>();
//饭店的厨师队列
private ArrayList<Chef> chefs = new ArrayList<Chef>();
private ExecutorService exec = Executors.newCachedThreadPool();
private static Random rand = new Random(47);
//饭店的订单队列
BlockingQueue<Order> orders = new LinkedBlockingQueue<Order>();
public Restaurant(ExecutorService exe,int nWaitPerson,int nChef){
exec = exe;
//预先为饭店分配好侍者和厨师
for(int i=0;i<nWaitPerson;i++){
WaitPerson waitPerson = new WaitPerson(this);
waitPersons.add(waitPerson);
exec.execute(waitPerson);
}
for(int i=0;i<nChef;i++){
Chef chef = new Chef(this);
chefs.add(chef);
exec.execute(chef);
}
}
//饭店任务主要是隔一段时间就产生一个顾客,并为这个顾客分配一个服务的侍者
public void run(){
try{
while(!Thread.interrupted()){
WaitPerson wp = waitPersons.get(rand.nextInt(waitPersons.size()));
Customer c = new Customer(wp);
exec.execute(c);
TimeUnit.MILLISECONDS.sleep(100);
}
}catch(InterruptedException ex){
System.out.println(this+"Interrupted");
}
System.out.println("Restaurant closing");
}
}
public class RestaurantWithQueues {
public static void main(String[] args) throws Exception{
ExecutorService exec = Executors.newCachedThreadPool();
//指定一个五个侍者,2个厨师的饭店
Restaurant restaurant = new Restaurant(exec,5,2);
exec.execute(restaurant);
System.out.println("Press ‘Enter‘ to quit");
System.in.read();
exec.shutdownNow();
}
}
三.汽车装配工厂仿真
问题描述:模拟一条汽车生产线;汽车的生产过程首先是生产底盘,然后在底盘上装配好发动机,动力传动系统,车轮,然后一辆车就生产完成啦。
仿真思路:封装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<Car1>{};
//建造底盘的类
//建好底盘以后就将放入相应的阻塞队列中,供后面的线程使用
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<Robot> pool = new HashSet<>();
public synchronized void add(Robot r){
pool.add(r);
notifyAll();
}
public synchronized void hire(Class<?extends Robot>robotType,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();
}
}
Thinking in Java---多线程仿真:银行出纳员仿真+饭店仿真+汽车装配工厂仿真
原文:http://blog.csdn.net/acm_lkl/article/details/50752343