首页 > 代码库 > 黑马程序员_交通灯管理系统

黑马程序员_交通灯管理系统

(1)项目的需求

模拟实现十字路口的交通灯管理系统逻辑,具体需求如下:

例如:

由南向而来去往北向的车辆 右转车辆

由东向而来去往南向的车辆 ---- 左转车辆

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  W2Spublic 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这个接口搭上关系的.

1

(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...");    }}
这个线程池可以设置线程启动的时间,具体线程启动时间如何安排的,我已经在代码中注释起来了.