JavaSE——Java交通系统


交通系统仿真


摘要:

本文首先对张孝祥老师交通灯系统的面试题,根据自己的理解进行了解析,并给出了自己编写的代码. 之后, 本文根据视频中张老师的解析, 比较了自己的解法和张老师解法的不同, 总结了优劣. 最后给出了总结.  


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. 枚举类内的可读性和等转换的运用还是比较满意的


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值