Android

操作系统课设 - 信号量机制 - 交通信号灯模拟

2017-08-08  本文已影响508人  谢小帅

Android 交通信号灯模拟 - Shuai-Xie - Github
Java 交通信号灯模拟 - Shuai-Xie - Githu

一、题目重述

二、设计思想

交通灯问题中的共享资源是十字路口的道路,在设计交通灯时,南北向和东西向的交通灯交替变化为绿色,所以南北行驶的汽车与东西向行驶的汽车不会有冲突,但是当同一方向出现了同时到达十字路口的汽车时,就出现冲突了,某一时刻道路资源设置为互斥信号量,当一辆车使用道路资源时,剩下的这一时刻出现的汽车(可能多辆)必须等待,当该汽车通过路口时,剩下的车辆仍然采用互斥的方式使用路口资源,按顺序通过十字路口,此时时间递增。

在模拟该问题情景时,要自定义交通灯和车辆信息。

主要影响调度的特征:汽车行驶方向(direction)和汽车出现时间(showtime)

  1. 首先,根据汽车的 showTime 对所有的汽车进行排序,按 showTime 从小到大的顺序排列,showTime 相同的汽车之间的先后顺序任意均可;
  2. 其次,根据汽车的 directon 对汽车分成东西南北四类汽车,将分好类的汽车对象按出现顺序由先到后存储到4个队列中:
    northCarsQueue 存储北向汽车信息,southCarsQueue 存储南向汽车信息,westCarsQueue 存储西向汽车信息,eastCarsQueue 存储东向汽车信息。
  3. 然后,每隔一定的时间(lightGreenSeconds),南北向和东西向的汽车交替通过路口,汽车对象出队列,此时根据 汽车出现时间 showTime,信号量数组 roadMutex,计时器 timeCounter 三者之间的关系 计算出汽车实际通过路口的时间 getActualPassTime。

三、数据结构

3.1 交通灯Light类

交通灯的特征:

/**
 * Created by shuai
 * on 2017/3/5.
 */
public class Light {
    private int direction;  // 灯的方向:东西,南北
    private int color;      // 灯的颜色:红,绿
    private int seconds;    // 交通灯持续时间

    public Light(int direction, int color, int seconds) {
        this.direction = direction;
        this.color = color;
        this.seconds = seconds;
    }

    public int getDirection() {
        return direction;
    }

    public int getColor() {
        return color;
    }

    public void setColor(int color) {
        this.color = color;
    }

    public String toString() {
        return "交通灯方向:" + direction + "\t" + "状态:" + color + "\t" + "颜色持续时间:" + seconds;
    }
}

3.2 汽车Car类

汽车包含的特征有:

import java.util.Random;

/**
 * Created by shuai
 * on 2017/3/5.
 */
public class Car {
    private String id;      // 车牌号
    private int direction;  // 行驶方向
    private int showTime;   // 出现时间,到达十字路口的时间

    public Car(int direction, int showTime) {
        this.id = generateCarID(); // Car内自带的随机生成车牌号的方法,随着车辆初始化随机生成一个车牌号
        this.direction = direction;
        this.showTime = showTime;
    }

    public String getId() {
        return id;
    }

    public int getDirection() {
        return direction;
    }

    public int getShowTime() {
        return showTime;
    }

    // 重载toString方法,打印车辆信息
    public String toString() {
        String direct;
        switch (direction) {
            case 0:
                direct = "North";
                break;
            case 1:
                direct = "South";
                break;
            case 2:
                direct = "West";
                break;
            case 3:
                direct = "East";
                break;
            default:
                direct = "";
        }
        return id + "\t" + direct + "\t" + showTime;
    }

    // 车牌号的组成一般为:省份+地区代码+5位数字/字母
    private static String generateCarID() {
        char[] provinceAbbr = { // 省份简称 4+22+5+3
                '京', '津', '沪', '渝',
                '冀', '豫', '云', '辽', '黑', '湘', '皖', '鲁', '苏', '浙', '赣',
                '鄂', '甘', '晋', '陕', '吉', '闽', '贵', '粤', '青', '川', '琼',
                '宁', '新', '藏', '桂', '蒙',
                '港', '澳', '台'
        };
        String alphas = "QWERTYUIOPASDFGHJKLZXCVBNM1234567890"; // 26个字母 + 10个数字
        Random random = new Random();
        String carID = "";

        // 省份+地区代码+·  如 湘A·
        carID += provinceAbbr[random.nextInt(34)]; // 注意:分开加,因为加的是2个char
        carID += alphas.charAt(random.nextInt(26)) + "·";

        // 5位数字/字母
        for (int i = 0; i < 5; i++) {
            carID += alphas.charAt(random.nextInt(36));
        }
        return carID;
    }
}

四、算法设计

4.1 创建问题情景

设置南北向和东西向的交通灯,再随机生成任意数量的汽车。

// 南北向和东西向各设置一个交通灯就可以
Light northLight = new Light(North, Green, 0); // 方向,颜色,时间
Light eastLight = new Light(East, Red, 0); // 时间不用指定,因为后面有timeCounter

// 随机生成车辆信息
Car[] cars = new Car[carNumber];
Random random = new Random();
for (int i = 0; i < carNumber; i++) { // foreach不行,因为是数组初始化
    cars[i] = new Car(random.nextInt(4), random.nextInt(timeRange) + 1);
    // new Car(int direction, int showTime) 随机生成车辆朝向和出现时间,时间范围[1, timeRange]
    // 在Car构造函数内部随机生成了车辆ID,即车牌号
}

4.2. 数据预处理

将汽车按照 direction 存入到 4 个方向队列中,存储顺序按照汽车出现时间 showTime 由小到大,稳定不稳定的排序都可以,因为同时到达路口的汽车没有时间上先后之分,而这些同时到达的汽车正是我们需要调度的对象。

  1. 汽车对象按照出现时间排序,因为出现时间是随机指定的。
// cars数组按车辆出现时间排序的
sortCarsByShowTime(cars);
private void sortCarsByShowTime(Car cars[]) {
    // copy车辆信息
    Car[] tmpCars = new Car[carNumber];
    System.arraycopy(cars, 0, tmpCars, 0, cars.length);

    // 对车辆信息按照出现时间排序
    int index = 0; // 遍历cars数组
    for (int i = 1; i <= timeRange; i++) { // showTime递增
        for (Car c : tmpCars) { // 遍历tmpCars
            if (c.getShowTime() == i) { // 找到showTime=i的车辆
                cars[index] = c;
                index++;
            }
        }
    }
}
  1. 汽车按照 direction 进入 4 个方向队列。
// 车辆信息进队列
Queue<Car> northCarsQueue = new LinkedBlockingQueue<>();
Queue<Car> southCarsQueue = new LinkedBlockingQueue<>();
Queue<Car> westCarsQueue = new LinkedBlockingQueue<>();
Queue<Car> eastCarsQueue = new LinkedBlockingQueue<>();
for (Car c : cars) {
    switch (c.getDirection()) {
        case North:
            northCarsQueue.add(c);
            break;
        case South:
            southCarsQueue.add(c);
            break;
        case West:
            westCarsQueue.add(c);
            break;
        case East:
            eastCarsQueue.add(c);
            break;
        default:
            break;
    }
}

// 集合方式遍历,元素不会被移除
System.out.println("生成的车辆信息");
for (Car c : northCarsQueue) System.out.println(c);
for (Car c : southCarsQueue) System.out.println(c);
for (Car c : westCarsQueue) System.out.println(c);
for (Car c : eastCarsQueue) System.out.println(c);
System.out.println();

4.3 信号量机制

某一时刻的道路作为互斥信号量,影响体现在汽车实际通过十字路口的时间。

实际经过时间 >= 到达路口时间

互斥信号量定义

// 一条路双向车道,设置2个信互斥信号量集
int[] northMutex = new int[100];
int[] southMutex = new int[100];
int[] westMutex = new int[100];
int[] eastMutex = new int[100];
/**
 * 获取车辆实际经过十字路口的时间
 *
 * @param roadMutex   道路互斥信号量集
 * @param showTime    汽车出现时间
 * @param timeCounter 计时器
 * @return actualPassTime 车辆实际通过路口的时间
 */
private int getActualPassTime(int[] roadMutex, int showTime, int timeCounter) {
    // timeCounter-1 确保timeLower落在正确范围内,取商运算
    int timeLower = (timeCounter - 1) / lightGreenSeconds * lightGreenSeconds + 1; // 时间下界

    // 汽车出现时间 < timeLower 重置出现时间,说明汽车等待到下一个绿灯
    if (showTime <= timeLower) showTime = timeLower;

    if (roadMutex[showTime] == 0) { // 该时刻的道路资源未被占用,可通过,直接返回showTime,并将roadMutex[showTime]置1
        roadMutex[showTime] = 1;
        return showTime;
    } else {                        // 这一时刻的道路资源已被占用了,不可通过
        int sum = 0;
        for (int i = showTime; i <= timeCounter; i++) { // 查询roadMutex数组,看自己排在队列的第几位
            if (roadMutex[i] == 1) { // =1 表示showTime之后的时刻的道路资源被占用
                sum++;
            }
        }
        roadMutex[showTime + sum] = 1; // 表示该车占用该一时刻的道路资源
        return showTime + sum; // 返回实际通过时间
    }
}
  1. 通过 timeCounter 求得绿灯刚开始亮的时间 timeLower(边界值划限);
  2. 如果 showTime <= timeLower,说明汽车已经等到这一个绿灯了,即该汽车没有在其出现时间所在的绿灯时间范围内通过路口,所以更新其 showTime 为 timeLower,这种情况下会使很多不同 showTime 的车辆出现时间都设为 timeLower,不过有两个原因可以证明这也是合理的:
    ① 上一个交通灯没有通过的车陆陆续续(即 showTime 不同)到达路口,因为都在这个路口等,所以到下一个交通灯的时候 showTime 就一样了;
    ② showTime 一样不会影响其先后关系,因为车辆已经入队列了,先后关系确定了。
  3. roadMutex[showTime] == 0 说明这一时段的道路未被占用,可以通过,该车辆通过的时候,设置 roadMutex[showTime] = 1,其他车辆在这一时刻不可同过,除非是反向的车辆,因为是双向车道,比如东向车在走,西向的也可以走;
  4. roadMutex[showTime] != 0 说明这一时段的道路已被占用,此时查询 roadMutex 数组,看自己排在队列的第几位,然后得到实际通过时间。

4.4 解决问题

int lightGreen; // 绿灯持续时间,用这个变量,是因为绿灯持续时间是慢慢减少为0的
int timeCounter = 0; // 时间计数器,与车辆实际通过路口的时间有关

// 一条路双向车道,设置2个互斥信号量集
int[] northMutex = new int[100];
int[] southMutex = new int[100];
int[] westMutex = new int[100];
int[] eastMutex = new int[100];

// 4个String存储不同方向的车辆通过信息
String northPassInfo = "";
String southPassInfo = "";
String westPassInfo = "";
String eastPassInfo = "";

// 只要还有车,队列就执行
while (!northCarsQueue.isEmpty() || !southCarsQueue.isEmpty() ||
        !westCarsQueue.isEmpty() || !eastCarsQueue.isEmpty()) {

    // 打印时间段信息 如 1----10s
    System.out.print((timeCounter + 1) + "----" + (timeCounter + lightGreenSeconds) + "s\t");

    // 调度车辆
    if (northLight.getColor() == Green) {       // 南北向车辆通过十字路口
        System.out.println("南北向绿灯亮\n");
        lightGreen = lightGreenSeconds;
        while (lightGreen-- > 0) { // 每经过一辆车花费时间为1,每花费1的时间最多通过2辆车,因为是双向的
            timeCounter++; // timeCounter与车辆实际通过路口的时间有关
            northPassInfo += passOneCarInfo(northCarsQueue, northMutex, timeCounter);
            southPassInfo += passOneCarInfo(southCarsQueue, southMutex, timeCounter);
        }

        // 打印南北向车辆通过信息
        System.out.println(northPassInfo);
        System.out.println(southPassInfo);

        northPassInfo = ""; // 重置String,循环使用
        southPassInfo = "";

        northLight.setColor(Red); // 交通灯颜色转换
        eastLight.setColor(Green);
    } else {                                    // 东西向车辆通过十字路口
        System.out.println("东西向绿灯亮\n");
        lightGreen = lightGreenSeconds;
        while (lightGreen-- > 0) { // 每经过一辆车花费时间为1
            timeCounter++;
            westPassInfo += passOneCarInfo(westCarsQueue, westMutex, timeCounter);
            eastPassInfo += passOneCarInfo(eastCarsQueue, eastMutex, timeCounter);
        }

        // 打印东西向车辆通过信息
        System.out.println(westPassInfo);
        System.out.println(eastPassInfo);

        westPassInfo = ""; // 重置String,循环使用
        eastPassInfo = "";

        eastLight.setColor(Red); // 交通灯颜色转换
        northLight.setColor(Green);
    }
}

通过一辆车 passOneCarInfo

// String存储一辆车的经过信息,便于后面结果的显示
private static String passOneCarInfo(Queue<Car> carsQueue, int[] roadMutex, int timeCounter) {
    String passInfo = "";
    if (!carsQueue.isEmpty()) {
        Car car = carsQueue.peek();
        if (car.getShowTime() <= timeCounter) { // 假定提前到了,在这一次绿灯亮一定可以穿行
            passInfo = car.toString() + "----" + getActualPassTime(roadMutex, car.getShowTime(), timeCounter) + "\n";
            carsQueue.remove(); // 满足条件,放行一辆车
        }
    }
    return passInfo; // 车辆出现信息 + 车辆经过时间
}

4.5 测试

设定条件

private static final int timeRange = 20;   // 模拟总时长
private static final int carNumber = 20;  // 车辆总数
private static final int lightGreenSeconds = 10; // 交通灯显示绿色的时长

生成的车辆信息

车牌号        朝向    出现时间
湘U·EM7P6    North   3
赣T·5EYJA    North   13

蒙A·2KX51    South   1
黑M·LPHN8    South   7
赣L·5TSL3    South   11
闽P·EWZMH    South   11

津G·X1J69    West    2
青F·PXF6N    West    12
苏C·L6Z7A    West    12
宁A·NTC5J    West    13
藏H·6VRJT    West    15
浙W·FYWU7    West    15
皖V·2NPLA    West    17

川U·LJSYS    East    10
桂F·EJEYK    East    12
赣S·GMB8C    East    15
港J·08QVC    East    16
港O·SR58D    East    16
台E·1H5IJ    East    18
蒙Y·YV5LD    East    18

调度结果

1----10s    南北向绿灯亮

湘U·EM7P6    North   3----3

蒙A·2KX51    South   1----1
黑M·LPHN8    South   7----7

11----20s   东西向绿灯亮

津G·X1J69    West    2----11
青F·PXF6N    West    12----12
苏C·L6Z7A    West    12----13
宁A·NTC5J    West    13----14
藏H·6VRJT    West    15----15
浙W·FYWU7    West    15----16
皖V·2NPLA    West    17----17

川U·LJSYS    East    10----11
桂F·EJEYK    East    12----12
赣S·GMB8C    East    15----15
港J·08QVC    East    16----16
港O·SR58D    East    16----17
台E·1H5IJ    East    18----18
蒙Y·YV5LD    East    18----19

21----30s   南北向绿灯亮

赣T·5EYJA    North   13----21

赣L·5TSL3    South   11----21
闽P·EWZMH    South   11----22

五、界面设计

5.1 题目展示界面

5.2 交通信号灯模拟界面

动画设计:

5.3 调度结果界面

上一篇下一篇

猜你喜欢

热点阅读