筆者觀看了張老師關於交通燈管理系統的視頻講解,按照要求自己重新編寫了程序。
1. 項目需求
(1)異步隨機生成按照各個路線行駛的車輛。
例如:
由南向而來去往北向的車輛 ---- 直行車輛
由西向而來去往南向的車輛 ---- 右轉車輛
由東向而來去往南向的車輛 ---- 左轉車輛
。。。
(2)信號燈忽略黃燈,只考慮紅燈和綠燈;
(3)應考慮左轉車輛控制信號燈,右轉車輛不受信號燈控制;
(4)具體信號燈控制邏輯與現實生活中普通交通燈控制邏輯相同,不考慮特殊情況下的控制邏輯。注:南北向車輛與東西向車輛交替放行,同方向等待車輛應先放行直行車輛而後放行左轉車輛;
(5)每輛車通過路口時間爲1秒(提示:可通過線程Sleep的方式模擬);
(6)隨機生成車輛時間間隔以及紅綠燈交換時間間隔自定,可以設置;
(7)不要求實現GUI,只考慮系統邏輯實現,可通過Log方式展現程序運行結果。
2. 需求分析
(1)點:異步生成要求我們使用多線程,每條車流都要有自己的線程。車輛生成間隔可以用Thread.sleep(new Random()...)來模擬;
(2)點:不考慮黃燈,說明信號燈總體上只有兩個狀態:放行和不放行;
(3)點:右轉車輛不受信號燈控制,因而右轉車輛的控制可以省略,實際上只需考慮8條車流;
(4)點:這個要求規定了信號燈的有限狀態機。按照日常生活經驗,左轉燈要單獨列出。這樣每個方向上的燈都需顯示三種信號:直行,左轉,停車。狀態機採取南北直行,全向左轉,東西直行的循環,以保證任一條道路上沒有對向駛來的車流;因爲信號燈需要定時轉換信號,所以需要單列一個信號燈線程,使用線程休眠方式來定時;
(5)點:車輛駛出道路需要1秒,需要創建放行線程定時放行車輛並刷新車輛數;
(6) 點:時間間隔通過相應線程的休眠時間來控制;
(7)點:爲了打印Log,需要單列一個監視器線程,監視8條車流的情況。無論是新車出現還是舊車放行導致剩餘車輛數有變化,都需要打印變化情況。
綜上,這個項目的難點是同時運行的線程較多,而且不同線程的打印語句有可能互相干擾,導致LOG上輸出的語句順序混亂。張老師用到了java.util.concurrent包中的併發線程工具。這些工具相比於Thread類中的方法,更爲簡潔和安全。不過筆者還是想用自學視頻中講到的多線程基本語句來寫。這樣做在代碼上更繁瑣一些,但是卻更熟悉。
3. 模塊分析
3.1 接口
import java.util.*;
//用常數代替字符串作爲標籤
interface Tag
{
//直行信號
public final int PASS = -1;
//左轉信號
public final int LEFT_PASS = -2;
//停車信號
public final int STOP = -3;
//信號間隔(秒)
public final long LAMP_PERIOD = 3;
//任一道路上的車輛出現間隔(最小值,秒)
public final double CAR_PERIOD_MIN = 2;
//最大值(秒)
public final double CAR_PERIOD_MAX = 3;
}
3.2 信號燈類
//信號燈類包括東西南北四個方向的信號燈,每個燈有PASS, LEFT_PASS, STOP三種信號
class Light implements Tag, Runnable
{
int N = PASS;
int S = PASS;
int W = STOP;
int E = STOP;
//鎖,用來規範顯示順序
Object lock;
public Light(Object lock)
{
this.lock = lock;
}
//將信號燈狀態的常數標籤轉換回字符串
public String translate(int signal)
{
if(signal == PASS)
return "PASS";
else if(signal == LEFT_PASS)
return "LEFT_PASS";
else
return "STOP";
}
//信號燈變化時打印新的信號
public void printLight()
{
System.out.println("N = " + translate(N) + ", S = " + translate(S) + ", W = " + translate(W) + ", E = " + translate(E));
}
//信號燈的有限狀態機
@Override
public void run()
{
long time = System.currentTimeMillis();
while(true)
{
if(N == PASS)
{
//改變信號
N = LEFT_PASS;
S = LEFT_PASS;
W = LEFT_PASS;
E = LEFT_PASS;
}
else if(N == LEFT_PASS)
{
N = STOP;
S = STOP;
W = PASS;
E = PASS;
}
else if(N == STOP)
{
N = PASS;
S = PASS;
W = STOP;
E = STOP;
}
//使用鎖來保證三行顯示語句連貫輸出
//顯示信號燈變化的時刻和變化後的信號
synchronized(lock)
{
System.out.println("");
System.out.println((double)(System.currentTimeMillis() - time)/1000 + "s");
printLight();
}
try
{
//信號燈定時切換信號
Thread.sleep(LAMP_PERIOD * 1000);
}
catch(Exception e){}
}
}
}
3.3 車流類
//每條路都是一個獨立線程,線程內隨機生成車輛並儲存該路上當前的車輛數
class Road implements Tag, Runnable
{
//當前車輛數
int total = 0;
@Override
public void run()
{
while(true)
{
//新出現一輛車
total++;
//等待一段隨機的時間
try
{
Thread.sleep((long) (1000 * (new Random().nextDouble() * (CAR_PERIOD_MAX - CAR_PERIOD_MIN) + CAR_PERIOD_MIN)));
}
catch(Exception e){}
}
}
}
3.4 放行類
//控制系統,放行車輛,並打印放行結束後各條路的車輛數
class Control implements Tag, Runnable
{
//由於向右轉無需交通燈控制,故關注4個方向的8條路,每個方向有直行和左轉兩條路
//北邊的直行路
Road N_road;
//北邊的左轉路
Road NL_road;
Road S_road;
Road SL_road;
Road W_road;
Road WL_road;
Road E_road;
Road EL_road;
Light light;
public Control(Road N_road, Road NL_road, Road S_road, Road SL_road, Road W_road, Road WL_road, Road E_road, Road EL_road, Light light)
{
this.N_road = N_road;
this.NL_road = NL_road;
this.S_road = S_road;
this.SL_road = SL_road;
this.W_road = W_road;
this.WL_road = WL_road;
this.E_road = E_road;
this.EL_road = EL_road;
this.light = light;
}
//放行車輛
@Override
public void run()
{
while(true)
{
try
{
//車輛駛出道路需要1秒
Thread.sleep(1000);
}
catch(Exception e){}
//放行後更改剩餘車輛數
if(light.N == LEFT_PASS)
{
NL_road.total = (NL_road.total > 0) ? NL_road.total - 1 : 0;
SL_road.total = (SL_road.total > 0) ? SL_road.total - 1 : 0;
WL_road.total = (WL_road.total > 0) ? WL_road.total - 1 : 0;
EL_road.total = (EL_road.total > 0) ? EL_road.total - 1 : 0;
}
else if(light.N == STOP)
{
W_road.total = (W_road.total > 0) ? W_road.total - 1 : 0;
E_road.total = (E_road.total > 0) ? E_road.total - 1 : 0;
}
else if(light.N == PASS)
{
N_road.total = (N_road.total > 0) ? N_road.total - 1 : 0;
S_road.total = (S_road.total > 0) ? S_road.total - 1 : 0;
}
}
}
}
3.5 監視器類
//監視器,當任何道路的車輛數因爲新車出現或舊車放行而改變時,顯示變化時間和變化後車輛數
class Monitor implements Tag, Runnable
{
Road N_road;
Road NL_road;
Road S_road;
Road SL_road;
Road W_road;
Road WL_road;
Road E_road;
Road EL_road;
int N_old = 0;
int NL_old = 0;
int S_old = 0;
int SL_old = 0;
int W_old = 0;
int WL_old = 0;
int E_old = 0;
int EL_old = 0;
Object lock;
//換行標誌
boolean changeLine = false;
public Monitor(Road N_road, Road NL_road, Road S_road, Road SL_road, Road W_road, Road WL_road, Road E_road, Road EL_road, Object lock)
{
this.N_road = N_road;
this.NL_road = NL_road;
this.S_road = S_road;
this.SL_road = SL_road;
this.W_road = W_road;
this.WL_road = WL_road;
this.E_road = E_road;
this.EL_road = EL_road;
this.lock = lock;
}
@Override
public void run()
{
long time = System.currentTimeMillis();
while(true)
{
if(N_road.total == N_old && NL_road.total == NL_old
&& S_road.total == S_old && SL_road.total == SL_old
&& W_road.total == W_old && WL_road.total == WL_old
&& E_road.total == E_old && EL_road.total == EL_old);
else
{
//用鎖保證下列語句是連貫輸出的
synchronized(lock)
{
changeLine = false;
//打印車輛數發生變化的時間
System.out.println((double)(System.currentTimeMillis() - time)/1000 + "s");
//若是放行導致的,打印放行情況
if(N_road.total < N_old) {System.out.print("Go N, "); changeLine = true;}
if(NL_road.total < NL_old) {System.out.print("Go NL, "); changeLine = true;}
if(S_road.total < S_old) {System.out.print("Go S, "); changeLine = true;}
if(SL_road.total < SL_old) {System.out.print("Go SL, "); changeLine = true;}
if(W_road.total < W_old) {System.out.print("Go W, "); changeLine = true;}
if(WL_road.total < WL_old) {System.out.print("Go WL, "); changeLine = true;}
if(E_road.total < E_old) {System.out.print("Go E, "); changeLine = true;}
if(EL_road.total < EL_old) {System.out.print("Go EL, "); changeLine = true;}
//打印變化後的車輛數
if(changeLine)
System.out.println("\nN S W E NL SL WL EL");
else
System.out.println("N S W E NL SL WL EL");
System.out.println(N_road.total + " " + S_road.total + " " + W_road.total + " " + E_road.total + " " + NL_road.total + " " + SL_road.total + " " + WL_road.total + " " + EL_road.total);
}
//更新車輛數
N_old = N_road.total;
NL_old = NL_road.total;
S_old = S_road.total;
SL_old = SL_road.total;
W_old = W_road.total;
WL_old = WL_road.total;
E_old = E_road.total;
EL_old = EL_road.total;
}
}
}
}
3.6 main類
public class Test implements Tag
{
public static void main(String[] args)
{
//生成鎖用來規範顯示順序
Object lock = new Object();
Light light = new Light(lock);
Road N_road = new Road();
Road S_road = new Road();
Road W_road = new Road();
Road E_road = new Road();
Road NL_road = new Road();
Road SL_road = new Road();
Road WL_road = new Road();
Road EL_road = new Road();
//開啓交通燈線程,交通燈開始工作
new Thread(light).start();
//開啓所有路的線程,開始生成車輛
new Thread(N_road).start();
new Thread(S_road).start();
new Thread(W_road).start();
new Thread(E_road).start();
new Thread(NL_road).start();
new Thread(SL_road).start();
new Thread(WL_road).start();
new Thread(EL_road).start();
//開啓控制系統線程,車輛開始放行
new Thread(new Control(N_road, NL_road, S_road, SL_road, W_road, WL_road, E_road, EL_road, light)).start();
//開啓監控器線程,打印日誌
new Thread(new Monitor(N_road, NL_road, S_road, SL_road, W_road, WL_road, E_road, EL_road, lock)).start();
}
}