交通系统仿真
摘要:
本文首先对张孝祥老师交通灯系统的面试题,根据自己的理解进行了解析,并给出了自己编写的代码. 之后, 本文根据视频中张老师的解析, 比较了自己的解法和张老师解法的不同, 总结了优劣. 最后给出了总结.
I. 需求:
1. 模拟车辆过过十字路口, 车辆随机驶入路口, 判断红绿灯是否可以通行, 不考虑黄灯
2. 先放一对方向(如North-South)直行,再左转, 之后放行另一对方向直行,再左转
3. 右转不受限
4. 车辆过街要1s
II. 思考过程: (此处为自己编写的思路,与张老师视频中的思路略有不同)
1. 红绿灯种类有限, 而且次序变化是固定的, 考虑将灯的变化定义成一个枚举类SignalEnum
2. 需要有一个控制中心去控制灯的变化, 定义一个类ControlCenter
3. 车辆随机驶入路口, 而车辆在此时就有了明确的行驶和转向, 这个类型可以和SignalEnum里面定义的类型匹配
——————————————————————————————————————————————————
1.1 考虑对面方向灯变化的一致性, 右转无需设定, 枚举一共有4种变化类型: NORTHSOUTH_STR, NORTHSOUTH_LEFT, EASTWEST_STR, EASTWEST_LEFT
1.2 考虑灯有红,绿两种情况, 用一个boolean green; 来记录红绿, green=true为绿, green=false为红
1.3 需要将green设置为private并提供isGreen()/ setGreen()两个方法判断/设置灯的红绿
1.4 为方便可读性, 定义setRed()函数, 于setGreen()操作相反
1.5 考虑灯的变绿顺序变化, 在枚举类中定义 abstract nextGreenSignal() 函数, 返回下一个变绿的枚举类
1.6 模拟1s中过道, 程序等1s; await(1s)
——————————————————————————————————————————————————
2.1 控制中心需要调用枚举类中的枚举元素,并通过isGreen/setGreen/setRed对SignalEnum元素的灯红绿变化控制
2.2 控制中心内提供greenDuration(); 方法, 控制路灯的时间
2.3 控制中心需要按顺序循环实现对SignalEnum元素红绿灯控制
2.4 因为每辆车要1s中的时间过街, currentGreenSignal 变红后要等1s再让下一个灯变绿
2.5 可将ControlCenter作为单独的线程存在
——————————————————————————————————————————————————
3.1 车辆随机驶入路口, 可以看做是一个线程进入程序运行
3.2 因为, 车辆需要根据自己的行驶判断相应SignalEnum元素红绿灯, 不放直接将 SignalEnum implements Runnable, 将SignalEnum.Xxxx传入线程, 让线程判断SignalEnum中的green
3.3 控制台也对SignalEnum中元素的green成员进行操作,所以加同步可以保证多线程对公共资源操作的数据安全
3.4 来一个路口的车可能很多, 所以会有多个一类的线程同时存在, 需要定义一个condition让他们红灯await, 当控制台端变绿时, 控制台端 signalAll
3.5 由于有四个方向, 更好的是将Lock,Condition和SignalEnum中的枚举类一一绑定, 这样, 控制台没换到一个currentGreenSignal时,就同步到等一类灯变绿的线程车上,去唤醒这一类的线程
3.6 考虑右转, 在枚举类型里添加一个 NSEW_RIGHT标记所有右转的车辆的Runnable操作对象, 此对象内部复写isGreen;setRed
3.7 传入线程的SignalEnum在线程中不支持, 建立CarThread extends Thread, 创建一个新的构造函数
III 代码:
1. SignalEnum枚举类定义交通灯的种类及功能
package cn.itcast.trafficSystemSimulation;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.*;
/*
* 1. 用枚举定义不同的信号灯情况, 分为 NORTHSOUT_STR,NORTHSOUTH_LEFT,EASTWEST_STR,
* EASTWEST_LEFT,(NSEW_RIGHT)
* 2. 需要将信号等的情况传给CarThread作为线程的Runnable的对象使用,实现Runnable --> 3.2
* 3. NSEW_RIGHT这两种情况只是为了给Car传入用时,考虑车辆右行总是无需停车的状况准备的 --> 3.6
* 对控制台控制红绿灯的方面没有用的
*/
public enum SignalEnum implements Runnable{ // --> 3.2
// 枚举列表开始 |--- --> 1.1
NORTHSOUTH_STR{
public SignalEnum nextGreenSignal(){
return NORTHSOUTH_LEFT;
}
},
NORTHSOUTH_LEFT{
public SignalEnum nextGreenSignal(){
return EASTWEST_STR;
}
},
EASTWEST_STR{
public SignalEnum nextGreenSignal(){
return EASTWEST_LEFT;
}
},
EASTWEST_LEFT{
public SignalEnum nextGreenSignal(){
return NORTHSOUTH_STR;
}
},
NSEW_RIGHT{ // --> 3.6
public SignalEnum nextGreenSignal(){
return NSEW_RIGHT;
}
public boolean isGreen(){return true;}
public void setRed(){};
};
// 枚举列表结束 ---|
// ---------------------- 控制台部分需要设置红绿灯部分方法 ----------------------------- //
private boolean green=false; // --> 1.2
public boolean isGreen(){return green;} // --> 1.3
public void setGreen(){green=true;} // --> 1.3
public void setRed(){green=false;} // --> 1.4
public abstract SignalEnum nextGreenSignal(); // --> 1.5
// --------------------- Runnable 接口部分复写内容 ---------------------------------- //
/*
* 复写run,调用checkSignal方法
*/
public void run(){
try{
checkSignal();
}catch(InterruptedException e){
e.printStackTrace();
}
}
private Lock lock=new ReentrantLock(); // --> 3.5
private Condition stopCondition=lock.newCondition(); // --> 3.5
/*
* checkSignal用来件事 green的值,如果false,await;
* 此处同步ControlCenter内部分代码,如果green=true, await线程唤醒,且可以出循环继续执行
*/
public void checkSignal() throws InterruptedException{
lock.lock(); // --> 3.3
try{
while(!isGreen()){
System.out.println(this+"'s "
+Thread.currentThread().getName()+" wait for red."
+" Green?"+isGreen());
stopCondition.await(); // --> 3.4
System.out.println(this+"'s "
+Thread.currentThread().getName()+" notified for green."
+" Green?"+isGreen());
}
stopCondition.await(1000,TimeUnit.MILLISECONDS); // --> 1.6
System.out.println(this+"'s "
+Thread.currentThread().getName()+ " acrossed");
}finally{lock.unlock();} // --> 3.3
}
/*
* getCondition返回一个对应枚举元素的Lock
*/
public Lock getLock(){
return lock;
}
/*
* getCondition返回一个对应枚举元素Lock的Condition
*/
public Condition getCondition(){
return stopCondition;
}
}
2. ControlCenter实现对枚举类SignalEnum的操作
package cn.itcast.trafficSystemSimulation;
import java.util.concurrent.locks.Lock;
/*
* ControlCenter实现对枚举类SignalEnum的操作
* - 实现红绿灯设置
* - 实现灯按顺序循环
* - 实现对CarThread的唤醒
*/
public class ControlCenter implements Runnable{ // --> 2.6
public void greenDuration(){ // --> 2.2
try {
Thread.sleep(5000 );
}catch (InterruptedException e){
e.printStackTrace();
}
}
public void run(){
SignalEnum currentGreenSignal=SignalEnum.NORTHSOUTH_STR;
while(true){
Lock lock=currentGreenSignal.getLock();
lock.lock();
try{
currentGreenSignal.setGreen(); // --> 2.1
System.out.println("-------Signal-------"
+currentGreenSignal+" is green!");
currentGreenSignal.getCondition().signalAll(); // --> 3.4
}finally{
lock.unlock();
}
this.greenDuration(); // --> 2.2
currentGreenSignal.setRed(); // --> 2.1
System.out.println("--------Signal-------"
+currentGreenSignal+" is red!"+" STOP!");
try {
Thread.sleep(1000); // --> 2.4
} catch (InterruptedException e) {
e.printStackTrace();
}
currentGreenSignal=currentGreenSignal.nextGreenSignal();
// --> 2.4
}
}
}
3. CarThread模拟车过路口
package cn.itcast.trafficSystemSimulation;
/*
* CarThread方法继承Thread
* - 需传入一个Enum类型,要重建构造函数
*/
public class CarThread extends Thread { // --> 3.7
public CarThread(SignalEnum target,String name){ // --> 3.7
super(target,name);
System.out.println(this+" comes here.");
}
public String toString(){
return "Car:"+getName();
}
}
IV 演示:
package cn.itcast.trafficSystemSimulation;
/*
* TrafficSystemSimulation演示结果 Car 0-5 对应 CarThread0-5
* Car 0-5 从不同方向同时来到路口
*/
public class TrafficSystemSimulation {
public static void main(String[] args) {
ControlCenter ctrlCenter=new ControlCenter();
Thread ctrlThread=new Thread(ctrlCenter);
CarThread CarThread0=new CarThread(SignalEnum.EASTWEST_STR,"Car 0");
CarThread CarThread1=new CarThread(SignalEnum.NORTHSOUTH_LEFT,"Car 1");
CarThread CarThread2=new CarThread(SignalEnum.EASTWEST_STR,"Car 2");
CarThread CarThread3=new CarThread(SignalEnum.EASTWEST_LEFT,"Car 3");
CarThread CarThread4=new CarThread(SignalEnum.EASTWEST_STR,"Car 4");
CarThread CarThread5=new CarThread(SignalEnum.NSEW_RIGHT,"Car 5");
ctrlThread.start();
CarThread0.start();
CarThread1.start();
CarThread2.start();
CarThread3.start();
CarThread4.start();
CarThread5.start();
}
}
输出结果:
Car:Car 0 comes here.
Car:Car 1 comes here.
Car:Car 2 comes here.
Car:Car 3 comes here.
Car:Car 4 comes here.
Car:Car 5 comes here.
-------Signal-------NORTHSOUTH_STR is green!
EASTWEST_STR's Car 0 wait for red. Green?false
EASTWEST_STR's Car 2 wait for red. Green?false
NORTHSOUTH_LEFT's Car 1 wait for red. Green?false
EASTWEST_LEFT's Car 3 wait for red. Green?false
EASTWEST_STR's Car 4 wait for red. Green?false
NSEW_RIGHT's Car 5 acrossed
--------Signal-------NORTHSOUTH_STR is red! STOP!
-------Signal-------NORTHSOUTH_LEFT is green!
NORTHSOUTH_LEFT's Car 1 notified for green. Green?true
NORTHSOUTH_LEFT's Car 1 acrossed
--------Signal-------NORTHSOUTH_LEFT is red! STOP!
-------Signal-------EASTWEST_STR is green!
EASTWEST_STR's Car 0 notified for green. Green?true
EASTWEST_STR's Car 2 notified for green. Green?true
EASTWEST_STR's Car 4 notified for green. Green?true
EASTWEST_STR's Car 4 acrossed
EASTWEST_STR's Car 0 acrossed
EASTWEST_STR's Car 2 acrossed
--------Signal-------EASTWEST_STR is red! STOP!
-------Signal-------EASTWEST_LEFT is green!
EASTWEST_LEFT's Car 3 notified for green. Green?true
EASTWEST_LEFT's Car 3 acrossed
--------Signal-------EASTWEST_LEFT is red! STOP!
-------Signal-------NORTHSOUTH_STR is green!
--------Signal-------NORTHSOUTH_STR is red! STOP!
-------Signal-------NORTHSOUTH_LEFT is green!
--------Signal-------NORTHSOUTH_LEFT is red! STOP!
-------Signal-------EASTWEST_STR is green!
--------Signal-------EASTWEST_STR is red! STOP!
V. 与张老师代码比较
1. 张老师思路:
a. 把每个路口的车等待和放行动作用集合Road的存和取完成, 实现FIFO(这一点比我的程序考虑的更好一点)
a.1 这个集合包含两个线程, 1个线程负责阻车,一个线程负责把放车(此处用到JDK1.5新的线程方法)
a.2 阻车线程是通过随机间隔时间向集合添加元素的方式实现的, 放车是通过线程以一定的频率监测红绿灯来实现的
b. 也用了一个枚举Lamp来表示灯的种类(不过,这里面张老师的枚举把所有的都举出以opposite去关联对面的车)
c. 也用了一个控制台LampController来控制灯的变化,这里用了一个Executor去以固定的频率转换绿的灯
2. 本质的区别:
a. 我的程序的思路是让等的变化去唤醒车辆线程, 张老师的程序是让没个Road的放车线程去每隔1s监测红绿灯的变化
b. 张老师的程序用了一个集合的思想表现出车排队, 最多有2倍的Road的种类(12)的线程(24),可以FIFO, 而我的程序没有做到这一点, 而是让每一个车作为一个线程,来多少个车就新开多少个线程, 当然这些线程多数处于等待状态, 唤醒时无法控制唤醒状态(相比较,张老师集合的思路更好, 一个推广是可以做双车道的优化)
VI. 总结
1. 程序还可以根据集合的思想进行优化
2. JDK1.5多线程的部分需要学习
3. 枚举类内的可读性和等转换的运用还是比较满意的