交通灯管理系统
(1)项目的需求
模拟实现十字路口的交通灯管理系统逻辑,具体需求如下:
例如:
由南向而来去往北向的车辆 ---- 直行车辆
由西向而来去往南向的车辆 ---- 右转车辆
由东向而来去往南向的车辆 ---- 左转车辆
平时开车过十字路口红绿灯的时候,也知道红绿灯运行的顺序
(1)任何方向的车,向右边转弯的时候,是不需要看红绿灯
(2)在十字路口,相对方向的红绿灯的工作方式是一样的,南相对与北,东相对与西,这把它分成两对
(3)红绿灯顺序,一对直行通道绿灯直行车辆,等直行变红,还是这对的车辆可以左转,等左转变红,就轮到下一对了.所以在设计程序的时候,只需要考虑一对的红绿灯情况即可.
Ø 信号灯忽略黄灯,只考虑红灯和绿灯。
Ø 应考虑左转车辆控制信号灯,右转车辆不受信号灯控制。
Ø 具体信号灯控制逻辑与现实生活中普通交通灯控制逻辑相同,不考虑特殊情况下的控制逻辑。
注:南北向车辆与东西向车辆交替放行,同方向等待车辆应先放行直行车辆而后放行左转车辆。
Ø 每辆车通过路口时间为1秒(提示:可通过线程Sleep的方式模拟)。
Ø 随机生成车辆时间间隔以及红绿灯交换时间间隔自定,可以设置。
(2)代码的思路与体现
首先对这个项目所设计到的名词都提炼出来,包括,汽车,马路,交通灯,通知交通灯的系统,张孝祥老师说了一句面向对象设计最经典的话,谁拥有数据,那么谁就来对外提供操作这些数据的方法,通过对这句话的理解,上面涉及到的名词,汽车是在马路上跑的,那么马路就得提供自己马路上汽车的数量,只有提供了汽车数量的变化,才能让红绿灯友好的运作起来.
Road类:构造方法初始化的时候要加上那路的线路,同时在路这个类中得提供一个容器去装路上的汽车.
package cn.interview.traffic; import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class Road { //交通工具集合,用来装交通工具的 List<String> vehicles = new ArrayList<String>(); //name是十字路上方向对方向路的名字 private String name = null; public Road(String name){ this.name = name; //启动一个线程,每隔一段时间,让一个交通工具上马路 ExecutorService pool = Executors.newSingleThreadExecutor(); pool.execute(new Runnable(){ public void run() { for(int i=0;i<1000;i++){ try { Thread.sleep((new Random().nextInt(10)+i) * 1000); } catch (InterruptedException e) { e.printStackTrace(); } vehicles.add(Road.this.name+"_"+i); } } }); //在Road对象的构造方法中启动一个定时器,每隔一秒检查该方向上的灯是否为绿 ScheduledExecutorService timer = Executors.newScheduledThreadPool(1); timer.scheduleAtFixedRate( new Runnable(){ public void run() { if(vehicles.size()>0){ boolean lighted = Lamp.valueOf(Road.this.name).isLighted(); if(lighted == true){ System.out.println(vehicles.remove(0)+" is traversing"); } } } }, 1, 1, TimeUnit.SECONDS); } }
Lamp类:2种状态,true(绿灯),false(红灯),还要提供红绿灯变化的情况,绿灯亮的情况必须要保证对立两个方向的绿灯都亮,灯红的时候同时要包装对应方向的灯也要变红,同时等变红的时候,在方法中得有一个返回值,就是把红灯变成绿灯,这样这个交通灯系统才有一个良好的循环.
package cn.interview.traffic; // S2N S2W E2W E2S N2S N2E W2E W2N S2E E2N N2W W2S public enum Lamp { //12个方向的枚举对象 S2N("N2S","S2W",false),S2W("N2E","E2W",false),E2W("W2E","E2S",false),E2S("W2N","S2N",false), N2S(null,null,false),N2E(null,null,false),W2E(null,null,false),W2N(null,null,false), S2E(null,null,true),E2N(null,null,true),N2W(null,null,true),W2S(null,null,true); //构造方法,第一个参数是对面方向的灯,第二个参数是下一个灯,第三个参数是灯的状态 private Lamp(String opposite,String next,boolean lighted){ this.opposite = opposite; this.next = next; this.lighted = lighted; } private Lamp(){ } private boolean lighted; //表示反方向的灯,用String类型,方便传入枚举的构造方法中去 private String opposite; private String next; //灯状态的方法 public boolean isLighted(){ return lighted; } //绿灯状态,无返回值 public void light(){ this.lighted = true; if(opposite==null){ Lamp.valueOf(opposite).light(); } System.out.println(name()+"lamp is green,下面有6个方向的灯可以通过"); } //红灯状态,有返回值,返回下一个灯 public Lamp blackOut(){ this.lighted = false; if(opposite !=null){ Lamp.valueOf(opposite).blackOut(); } Lamp nextLamp = null; if(next != null){ nextLamp = Lamp.valueOf(next); System.out.println("绿灯从"+name()+"----->切换为"+next); nextLamp.light(); } return nextLamp; } }
LampController类:我们默认S2N方向的灯开始是绿灯,这里如何进行交通灯时间上的切换,用到了多线程的技术,我会在总结的时候,把枚举和多线程的知识补充上去.
package cn.interview.traffic; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class LampController { private Lamp currentLamp; public LampController(){ currentLamp = Lamp.S2N; currentLamp.light(); //这个也是用多线程技术,在控制交通灯变化的时间 ScheduledExecutorService timer = Executors.newScheduledThreadPool(1); timer.scheduleAtFixedRate( new Runnable(){ public void run() { currentLamp = currentLamp.blackOut(); } }, 10, 10, TimeUnit.SECONDS); } }
MainClass类: 直接用数组new出12条路,然后在把newRoad类,系统就能运行了.
package cn.interview.traffic; public class MainClass { public static void main(String[] args) { //12个路口定义一个数组,把这些路口加到路上去 String[] directions = {"S2N","S2W","E2W","E2S","N2S","N2E","W2E","W2N","S2E","E2N","N2W","W2S"}; for(int i=0;i<directions.length;i++){ new Road(directions[i]); } //开始跑交通灯了 new LampController(); } }
(3)总结
这个项目需要对十字路口红绿灯的运行模式十分的熟悉,在对时间上的处理,比如汽车上路的时间,交通灯变化的时间,要到了JDK1.5之后的新的多线程技术,同时对于枚举的作用,在这个项目中也发挥的淋漓精致,省去了很多繁琐的代码,整个项目感觉
第一: 前期对于对象的提炼和哪个对象拥有哪些方法,这个真是需要一定的代码功力,才能完美的将对应的方法放到对应的对象去上,
第二:JDK1.5多线程Executors可以解决时间上的问题
第三:枚举的使用,简化了代码的书写,同时看完张老师的高新技术后,对于枚举的运用,对于具体问题,把枚举这个类的构造方法设计好,在把相关的参数传入对应的枚举对象中去,这点真心太重要了,在枚举传递参数的时候,一般使用的是String,而后面需要用String去调用方法是行不同的,那么这个时候枚举中的valueOf()就起作用了.
----------------------------------------------------------------------------------------------------------------------------------------------------
通过交通灯的项目,对JDK1.5后,出现的多线程以前新的技术的补充(线程池)
线程池的基本思想还是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理。当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源。
Java5的线程池分好多种:具体的可以分为两类,固定尺寸的线程池、可变尺寸连接池。
在使用线程池之前,必须知道如何去创建一个线程池,在Java5中,需要了解的是java.util.concurrent.Executors类的API,这个类提供大量创建连接池的静态方法,是必须掌握的。
(1)固定大小的线程池,newFixedThreadPool:
package cn.thread; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Test01 { public static void main(String[] args) { //创建容量是5的线程池 ExecutorService pools = Executors.newFixedThreadPool(3); //创建5个线程出来 Thread t1 = new MyThread(); Thread t2 = new MyThread(); Thread t3 = new MyThread(); Thread t4 = new MyThread(); Thread t5 = new MyThread(); //将5个线程装入池子中去 pools.execute(t1); pools.execute(t2); pools.execute(t3); pools.execute(t4); pools.execute(t5); //关闭线程池 pools.shutdown(); } } class MyThread extends Thread{ public void run(){ System.out.println(Thread.currentThread().getName()+" is running...."); /*线程池中参数为5的时候,输出的情况 pool-1-thread-2is running.... pool-1-thread-4is running.... pool-1-thread-1is running.... pool-1-thread-3is running.... pool-1-thread-5is running.... * * 参数为3,输出的情况 * pool-1-thread-2 is running.... pool-1-thread-3 is running.... pool-1-thread-1 is running.... pool-1-thread-2 is running.... pool-1-thread-3 is running.... */ } }
观察参数是3和5的时候,sop输出的情况,newFixedThreadPool是指定在线程池中,有几个线程可以住在这里面,超过了这个数字的话,线程就住不进来了,其次,加入线程池的线程属于托管状态,线程的运行不受加入顺序的影响。
(2)单任务线程池,newSingleThreadExecutor
//ExecutorService pools = Executors.newFixedThreadPool(3); ExecutorService pools = Executors.newSingleThreadExecutor(); sop的内容 pool-1-thread-1 is running.... pool-1-thread-1 is running.... pool-1-thread-1 is running.... pool-1-thread-1 is running.... pool-1-thread-1 is running....
可以看出,每次调用execute方法,其实最后都是调用了thread-1的run方法。查看API,就能发现execute方法中,就一个Runnable可运行的任务.而根据以前的多线程的知识,实现多线程的两种方式,其实最后都和Runnable这个接口搭上关系的.
(3)三、可变尺寸的线程池,newCachedThreadPool:
这种方式的特点是:可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。
(4)四、延迟连接池,newScheduledThreadPool 上面的交通灯和汽车时间的变化,就是用到的这个
package cn.thread; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class Test2 { public static void main(String[] args) { //这个线程池的特点就是可以在给定的时间后运行 ScheduledExecutorService pool = Executors.newScheduledThreadPool(1); Thread t1 = new MyThread02(); Thread t2 = new MyThread02(); Thread t3 = new MyThread02(); pool.execute(t1); pool.schedule(t2,30,TimeUnit.SECONDS);//t1执行完毕后,30秒后,t2才执行T pool.scheduleAtFixedRate(t3,100,100,TimeUnit.SECONDS);//100秒后执行,如果还有的话在100+100执行,还有的话100+100*n执行 pool.shutdown(); } } class MyThread02 extends Thread{ public void run(){ System.out.println(Thread.currentThread().getName()+"is running..."); } }
这个线程池可以设置线程启动的时间,具体线程启动时间如何安排的,我已经在代码中注释起来了.