---------------------- android开发、Java培训、期待与您交流! ----------------------
需求分析:
办理银行业务的客户都要有一个号码, 这些号码都放在一个号码机器里面. 由于3中用户的号码都是相对独立的,普通客户取了1号 , VIP客户同样可以取到1号, 但是银行中的取号机可以取3种类型的号码, 所以取号机应该设计为单例的。窗口通过叫号的方式来提醒客户来办理业务, 所以需要一个号码管理器来生成新的号码和告诉窗口轮到哪些号码的客户办理业务.服务窗口分为3种窗口, 可以为号码管理器提供的客户服务
号码机器:
号码机器中有3个属性,分别对应3种号码. 3种号码都是相互独立的,所以号码机器应该设置为单例的.
import java.util.*; class NumberManager { privateintlastNumber=1; //号码 private List queueNumber= new ArrayList();//集合,用于存放号码 //产生新的号码,并把号码存入集合 public synchronizedint generateNumber (){ queueNumber.add(lastNumber); returnlastNumber++; } //提取最前面的号码去办理业务 public synchronizedint fetchNumber(){ if(queueNumber.size()>0) returnqueueNumber.remove(0); else return 0; } }
服务窗口:
服务窗口中有一个开始服务的方法,这个方法里面封装了3个线程,分别是为普通客户, 快速客户和VIP客户服务的窗口.每个窗口通过NumberMachine来获取要服务的号码, 普通窗口如果获取到了要服务的号码,就会随机产生服务用时,并且打印出来, 如果没有获取到号码,就休息1秒. 快速窗口和VIP窗口也要获取号码,如果没有获取到对应的号码, 就会去获取普通客户的号码 .为了知道获取到哪种类型的客户, 就要用枚举来限定3种类型, 枚举中的对象分别是:
COMMON , EXPRESS, VIP
import java.util.Random; import java.util.concurrent.Executors; publicclass ServiceWindow { private CustomerType type = CustomerType.COMMON; privateintwindowId = 1; // 窗口号 publicvoid setType(CustomerType type) { this.type = type; } publicvoid setWindowId(int windowId) { this.windowId = windowId; } publicvoid start() { Executors.newSingleThreadExecutor().execute(new Runnable() { @Override publicvoid run() { // TODO Auto-generated method stub // 通过号码机取号 while (true) { switch (type) { caseCOMMON: commonService(); break; caseEXPRESS: expressService(); break; caseVIP: vipService(); break; } } } }); } //普通服务窗口 privatevoid commonService() { String windowName = "第" + windowId + "号" + type + "窗口"; // 窗口名 System.out.println(windowName + "正在开始获取普通服务!"); Integer serviceNumber = NumberMachine.getInstance() // 取号 .getCommonManager().fetchNumber(); if (serviceNumber != null) { // 如果取到号码 System.out.println(windowName + "正在为" + serviceNumber + "号" + type + "客户服务~!!"); int costTime = new Random().nextInt(9000) + 1000; try { Thread.sleep(costTime); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out .println(windowName + "为普通客户服务,一共耗时 : ( " + costTime/1000 + " ) 秒!!"); } else { System.out.println(windowName + "没有获取到普通服务,休息1秒!"); try { Thread.sleep(1000); } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } } } //快速服务窗口 privatevoid expressService(){ String windowName="第"+windowId+"号"+type+"窗口"; //取号 System.out.println(windowName + "正在开始获取普通服务!"); Integer serviceNumber=NumberMachine.getInstance().getExpressManager().fetchNumber(); if(serviceNumber!=null){ System.out.println(windowName+"正在为"+type+"客户服务"); try { Thread.sleep(1000); } catch (Exception e) { // TODO: handle exception } System.out.println(windowName+"为"+type+"客户服务共用时 : ( 1 )秒!!"); }else{ System.out.println(windowName+"没有获取到"+type+"业务."); //当没有获取到快速服务的时候,就去获取普通服务 commonService(); } } //VIP 服务窗口 privatevoid vipService(){ String windowName="第"+windowId+"号"+type+"窗口"; System.out.println(windowName + "正在开始获取普通服务!"); Integer serviceName=NumberMachine.getInstance().getVIPManager().fetchNumber(); if(serviceName!=null){ System.out.println(windowName+"正在为"+type+"客户服务!"); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(windowName+"为"+type+"客户服务共用时 : ( 1 ) 秒"); }else{ System.out.println(windowName+"没有获取到"+type+"业务."); commonService(); } } }
主函数:
要在主函数中创建 4个普通窗口 , 1个快速窗口和1个VIP窗口,还要通过线程来按照一定时间创建客户.
import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; publicclass MainClass { publicstaticvoid main(String[] args) { //不同客户出现的频率(时间单位为秒) finalint com=1; finalint express=3; finalint vip=6; //创建普通服务窗口 for (int i = 1; i < 5; i++) { ServiceWindow common=new ServiceWindow(); common.setType(CustomerType.COMMON); common.setWindowId(i); common.start(); } //创建快速服务窗口 ServiceWindow expressWin = new ServiceWindow(); expressWin.setType(CustomerType.EXPRESS); expressWin.start(); //创建VIP服务窗口 ServiceWindow vipWin = new ServiceWindow(); vipWin.setType(CustomerType.VIP); vipWin.start(); //创建普通客户 Executors.newScheduledThreadPool(1).scheduleAtFixedRate( new Runnable(){ @Override publicvoid run() { // TODO Auto-generated method stub Integer serviceNumber=NumberMachine.getInstance().getCommonManager().generateNumber(); System.out.println("第"+serviceNumber+"普通客户等待服务!"); } }, 0, com, TimeUnit.SECONDS ); //创建快速客户 Executors.newScheduledThreadPool(1).scheduleAtFixedRate( new Runnable(){ @Override publicvoid run() { // TODO Auto-generated method stub Integer serviceNumber=NumberMachine.getInstance().getExpressManager().generateNumber(); System.out.println("第"+serviceNumber+"快速客户等待服务!"); } }, 0, express, TimeUnit.SECONDS ); //创建VIP客户 Executors.newScheduledThreadPool(1).scheduleAtFixedRate( new Runnable(){ @Override publicvoid run() { // TODO Auto-generated method stub Integer serviceNumber=NumberMachine.getInstance().getVIPManager().generateNumber(); System.out.println("第"+serviceNumber+"VIP客户等待服务!"); } }, 0, vip, TimeUnit.SECONDS ); } }
总结:
1.关于synchronized
通过 private 关键字来保证数据对象只能被方法访问。在多线程的情况下,由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题,synchronized 方法控制对类成员变量的访问:每个类实例对应一把锁,每个 synchronized 方法都必须获得调用该方法的类实例的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。这种机制确保了同一时刻对于每一个类实例,其所有声明为 synchronized 的成员函数中至多只有一个处于可执行状态(因为至多只有一个能够获得该类实例对应的锁),从而有效避免了类成员变量的访问冲突(只要所有可能访问类成员变量的方法均被声明为 synchronized)。
简单的说:用Synchronized修饰的方法要访问同一个资源时,只会执行其中的一个,直到该方法访问完后释放了该资源,解除该锁,下一个方法可以访问了。
缺陷:如果声明中包含一个线程类的run(),由于线程的特点,该方法在进程的周期中会一直运行,一直保持锁定,那么该类中其他的synchronized方法就一直无法调用需要访问的资源。之后引入了sychronized块。
2.单例设计
教程中提到由于号码管理器在整个系统中始终只有一个,因此需要设计成单例。
首先什么叫单例,单例的作用和创建方法
在我们的Java应用程序中,随着应用程序的运行会创建出很多对象。但有时候我们希望在创建某类对象时,无论创建多少次该类对象只有一份在内存中。这就是单例模式。如果我们要实现单例模式首先要保证构造函数私有化,即不允许用户随意调用我本类的构造函数,因为只要通过new操作创建对象,必定要在堆中开辟空间,这样就无法实现单例了。
简单的说,就是为了保证在程序运行的过程中全局只有一个该类的实例对象;创建方法是把构造方法私有化。
进过查资料,疑惑。NumberMachine类单例是为了保证该类对象只有一个。本人是这么理解的,表示的是号码机器一次自能给一个人取号。还有的疑问是关于在numberMachine类内new3个numberManager对象。google,baidu不够人性化。
3.线程池
线程池的作用:
线程池作用就是限制系统中执行线程的数量。
根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;少了浪费了系统资源,多了造成系统拥挤效率不高。用线程池控制线程数量,其他线程排队等候。一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待进程,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程池中有等待的工作线程,就可以开始运行了;否则进入等待队列。
为什么要用线程池:
1.减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务
2.可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
java中util包中有一个Execute接口。其下面的子类接口中有各种控制线程操作的方法。
1、newFixedThreadPool创建一个定长的线程池,每当提交一个任务就创建一个线程,直到达到池的最大长度。 2、newCachedThreadPool创建一个可缓存的线程池,如果当前线程池的长度超过了处理的需要时,它可以灵活地回收空闲的线程,当需求增加时,它可以灵活地增加新的线程,不会对池的长度做任何限制。 3、newSingleThreadExecutor创建一个单线程化的Executor,只创建唯一的工作者线程来执行任务,如果这个线程异常结束,会有另一个取代它,保证顺序执行。 4、newScheduleThreadPool创建一个定长的线程池,而且支持定时的以及周期性的任务执行,类似于Timer。 5、newFixedThreadPool和newCachedThreadPool返回通用目的的ThreadPoolExecutor实例。直接使用ThreadPoolExecutor也能创建更加专有的的Executor。
---------------------- android开发、java培训、期待与您交流! ----------------------