欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

使用 Java 的 Lock 锁实现办公楼中的电梯载客程序

程序员文章站 2022-05-03 23:44:07
使用 Java 的 Lock 锁实现办公楼中的电梯载客程序 1.0该 demo 已在 gitee 上开源,git 链接,欢迎 star !程序的基本介绍与需求:大楼只有一架电梯,楼层可自定义,每层楼 在不同时间点 有数量不定 的乘客需要上下楼。乘客在进入电梯前要选择上楼或下楼,进入电梯后选择楼层。一楼乘客不能下楼,顶楼乘客不能上楼。电梯运行顺序是先向上走,再向下;例如1楼的人去10楼,中途5楼有人要去1楼,先将1楼的人送到10楼,中途不在5楼停顿,之后再送5楼的人去1楼。简单程序的...

使用 Java 的 Lock 锁实现办公楼中的电梯载客程序 1.0

该 demo 已在 gitee 上开源,git 链接,欢迎 star !

程序的基本介绍与需求:

  1. 大楼只有一架电梯,楼层可自定义,每层楼 在不同时间点 有数量不定 的乘客需要上下楼。
  2. 乘客在进入电梯前要选择上楼或下楼,进入电梯后选择楼层。
  3. 一楼乘客不能下楼,顶楼乘客不能上楼。
  4. 电梯运行顺序是先向上走,再向下;例如1楼的人去10楼,中途5楼有人要去1楼,先将1楼的人送到10楼,中途不在5楼停顿,之后再送5楼的人去1楼。

简单程序的 demo,不包含业务逻辑:

/**
 * @author Ironhide
 * @since 2020-11-13
 */
public class LockTest {

    /**
     * 载客事件数量,为0表示没有载客需求
     */
    private static int eventCount = 0;

    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        Condition personCondition = lock.newCondition();
        Condition eleCondition = lock.newCondition();

        //开启乘客的线程
        new Thread(() -> {
            while (true) {

                lock.lock();
                try {
                    ++eventCount;
                    System.out.println("我要坐电梯");

                    eleCondition.signal();
                    personCondition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }

                System.out.println("我上电梯了,我要去XX楼");

                lock.lock();
                try {
                    //等电梯去目的地
                    eleCondition.signal();
                    personCondition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }

                --eventCount;
                System.out.println("我到达目的地了......");

            }
        }).start();

        //主线程执行电梯程序
        while (true) {
            lock.lock();
            try {
                if (eventCount != 0) {
                    System.out.println("电梯来接人了");
                    personCondition.signal();
                    eleCondition.await();

                    System.out.println("电梯去目的地");
                    personCondition.signal();
                }

                eleCondition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
}

无限打印:

电梯来接人了
我上电梯了,我要去XX楼
电梯去目的地
我到达目的地了......

乘客的步骤分为三步【选择上下楼、进入电梯选择楼层、下电梯】,乘客在这三步操作以外都是等待状态。
|
电梯步骤则为一步【电梯去指定楼层】,这两步以外也是等待。
|
总步骤:人选择上下楼 》电梯去指定楼 》人进入电梯 》电梯去指定楼 》人下电梯

Lock 锁:
|
Lock 跟关键字 synchronized 的用法都是锁住一段代码块,被锁住的代码块在某一时刻只能有一个线程执行。Lock 可以 new 出多个 Condition 对象,Condition 提供的方法可以管理线程间的协作(例如A先执行,B再执行,A再执行),Condition 提供的signal()await()用法与 Object 的notify()wait()方法一致。
|
再来看电梯与乘客线程的交互:

  1. 程序运行,如果电梯先拿到锁则直接eleCondition.await();进入等待状态,并释放锁;如果乘客先拿到锁,选择上下楼,再释放锁personCondition.await();进入等待状态;在乘客等待之前也调用了eleCondition.signal();,是为了应对 如果电梯在等待状态时唤醒电梯线程 的情况。
  2. 这时eventCount已经大于 0 了,电梯要先去接人,到达指定楼层后释放锁eleCondition.await();开始等待,并唤醒乘客的线程personCondition.signal();
  3. 乘客这时进入电梯选择楼层,释放锁开始等待,唤醒电梯线程。
  4. 电梯去往目的楼层,到达楼层后释放锁开始等待,唤醒乘客线程。
  5. 乘客到目的地下电梯。依次循环。

|
如果以上代码与逻辑没有问题,后面只需要填充业务逻辑即可。

代码一共四个类、乘客线程类、电梯线程类、电梯类、main 方法类

  1. main 方法用线程池直接开【总楼层数+1电梯线程】个线程
  2. 乘客线程控制乘客行为
  3. 电梯线程控制电梯行为
  4. 电梯类,记录电梯当前所在楼层、方向、电梯人数等,计算电梯下一次的目标楼层,反转电梯方向等。

如何计算电梯下一次的目标:

用了两个数组,数组长度为楼层数,每个元素代表一个楼层,用来标记电梯方向为上/下时需要停留的楼层;例如数组【0,0,1,0,0】表示电梯要在 3 层停留;要停留楼层可能是上客也可能是下客。

Elevator:

/**
 * @author Ironhide
 * @since 2020-11-12
 * <p>
 * 电梯类
 */
class Elevator {

    /**
     * 总楼层数
     */
    int totalFloors;
    /**
     * 当前电梯所在楼层
     */
    private int eFloor = 1;
    /**
     * 电梯方向,true为上升,反之下降
     */
    private boolean direction = true;
    /**
     * 当前电梯人数
     */
    private int peopleNum = 0;
    /**
     * 载客事件数量,为0表示没有载客需求
     */
    private int eventCount = 0;
    /**
     * 当direction为true,电梯在上升状态时
     * 电梯需要停留的楼层数组,当前数组元素大于0时表示要停留
     * 大于1时表示该层有多个上客或下客需求
     */
    private int[] upTarget;
    /**
     * 当direction为false,电梯在下降状态时电梯需要停留的楼层数组
     * 与upTarget意义一致
     */
    private int[] downTarget;

    Elevator(int totalFloors) {
        this.totalFloors = totalFloors;
        this.upTarget = new int[totalFloors];
        this.downTarget = new int[totalFloors];
    }

    int geteFloor() {
        return eFloor;
    }

    /**
     * 电梯所在楼层的增减
     */
    void seteFloor(boolean bn) {
        eFloor = bn ? eFloor + 1 : eFloor - 1;
    }

    /**
     * 获取电梯下一次要去的层数
     */
    int getNextTarget() {
        //当没有载客事件时,将电梯停到1楼
        if (eventCount == 0) {
            direction = false;
            return 1;
        }

        //当电梯向上到达顶层时/电梯向下到达一层时 换方向
        if (eFloor == totalFloors && isDirection()) {
            setDirection();
        } else if (eFloor == 1 && !isDirection()) {
            setDirection();
        }

        //电梯上升方向
        if (direction) {
            for (int i = eFloor; i < upTarget.length; i++) {
                //例如eFloor为4层,判断第5层是否要上下客,如果有就返回5
                if (upTarget[i] != 0) {
                    return i + 1;
                }
            }

            //防止无限循环
            if (eFloor == 1) {
                return 1;
            }
            //当上升方向没有目标时,转换方向,并递归该方法
            setDirection();
            return getNextTarget();
        } else {
            for (int i = eFloor; i > 1; i--) {
                //例如eFloor为2层,判断第1层是否要上下客,如果有就返回1
                if (downTarget[i - 2] != 0) {
                    return i - 1;
                }
            }

            //当方向为下降且也没有目标时,转换方向,在第一层至电梯当前层遍历upTarget
            for (int i = 0; i < upTarget.length; i++) {
                if (upTarget[i] != 0) {
                    //当前电梯是向下方向,如果目标在电梯层数以上,则要换方向
                    if (i + 1 > eFloor) {
                        direction = true;
                    }
                    return i + 1;
                }
            }

            return 1;
        }
    }

    boolean isDirection() {
        return direction;
    }

    private void setDirection() {
        this.direction = !direction;
    }

    int getEventCount() {
        return eventCount;
    }

    /**
     * bn为true是上客
     * 反之下客
     */
    synchronized void setPeopleNum(boolean bn) {
        peopleNum = bn ? peopleNum + 1 : peopleNum - 1;
    }

    int getPeopleNum() {
        return this.peopleNum;
    }

    /**
     * 乘客选择上楼还是下楼,操作电梯上行下行的数组
     *
     * @param pFloor 乘客所在楼层
     * @param isUp   为true表示乘客要往上走
     * @return 改变的是upTarget数组返回true,反之false,为了方便调用pClearUpOrDown方法
     */
    synchronized boolean pSetUpOrDown(int pFloor, boolean isUp) {
        eventCount++;
        boolean bn = true;

        if (isUp) {
            //电梯向上或向下,人向上
            upTarget[--pFloor]++;
        } else {
            //电梯向上或向下,人向下,两种特殊情况【人所在层数高于电梯所在层数,不管电梯是否上升,都对upTarget++,要先接到人】
            if (pFloor > eFloor) {
                upTarget[--pFloor]++;
            } else {
                //人比电梯低时,在电梯下降时接人
                downTarget[--pFloor]++;
                bn = false;
            }
        }

        return bn;
    }

    /**
     * 乘客进入电梯后清除之前选择上楼下楼的操作
     *
     * @param pFloor 乘客所在楼层
     * @param bn     pSetUpOrDown方法返回值
     */
    synchronized void pClearUpOrDown(int pFloor, boolean bn) {
        if (bn) {
            upTarget[--pFloor]--;
        } else {
            downTarget[--pFloor]--;
        }
    }

    /**
     * 乘客进入电梯后清除之前选择目标楼层
     *
     * @param pTarget 乘客选择的目标楼层
     * @param isUp    乘客选择上楼下楼
     */
    synchronized void pSetFloor(int pTarget, boolean isUp) {
        if (isUp) {
            upTarget[--pTarget]++;
        } else {
            downTarget[--pTarget]++;
        }
    }

    /**
     * 乘客下电梯时,清除该乘客的事件
     * 对电梯上行下行数组进行-1操作
     */
    synchronized void pClearFloor(int pTarget, boolean isUp) {
        eventCount--;

        if (isUp) {
            upTarget[--pTarget]--;
        } else {
            downTarget[--pTarget]--;
        }
    }

}

Elevator 类中加了 synchronized 标记的方法,在运行环境中可能会被多个线程同时调用。

ElevatorThread:

/**
 * @author Ironhide
 * @since 2020-11-18
 */
class ElevatorThread implements Runnable {

    private Elevator elevator;
    private ReentrantLock lock;
    private Condition personCondition;
    private Condition elevatorCondition;

    ElevatorThread(Elevator elevator, ReentrantLock lock, Condition personCondition, Condition elevatorCondition) {
        this.elevator = elevator;
        this.lock = lock;
        this.personCondition = personCondition;
        this.elevatorCondition = elevatorCondition;
    }

    @Override
    public void run() {
        while (true) {
            lock.lock();
            try {
                if (elevator.getEventCount() != 0) {
                    int eTarget = elevator.getNextTarget();

                    while (eTarget != elevator.geteFloor()) {
                        //上一层楼睡一秒钟
                        TimeUnit.SECONDS.sleep(1);

                        int eTargetTemp;
                        if (elevator.isDirection()) {
                            //例:当电梯在1层,目标为8层时,电梯在上升过程中,5层乘客要上楼,将eTarget改为5;电梯每上一层都获取一次目标
                            if ((eTargetTemp = elevator.getNextTarget()) < eTarget) {
                                eTarget = eTargetTemp;
                            }

                            elevator.seteFloor(true);
                        } else {
                            //同理,电梯下降过程中,eTargetTemp大于eTarget,则更新
                            if ((eTargetTemp = elevator.getNextTarget()) > eTarget) {
                                eTarget = eTargetTemp;
                            }

                            elevator.seteFloor(false);
                        }

                        System.out.println("电梯方向为" + (elevator.isDirection() ? "上" : "下") + ",目标为" + eTarget + "楼,当前在" + elevator.geteFloor() + "楼");
                    }

                    personCondition.signalAll();
                } else {
                    System.out.println("电梯无目标...");
                }

                elevatorCondition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
}

FloorThread:

/**
 * @author Ironhide
 * @since 2020-11-18
 */
class FloorThread implements Runnable {

    /**
     * pFloor 乘客所在楼层
     */
    private int pFloor;
    private Elevator elevator;
    private Lock lock;
    private Condition personCondition;
    private Condition elevatorCondition;

    FloorThread(int pFloor, Elevator elevator, ReentrantLock lock, Condition personCondition, Condition elevatorCondition) {
        this.pFloor = pFloor;
        this.elevator = elevator;
        this.lock = lock;
        this.personCondition = personCondition;
        this.elevatorCondition = elevatorCondition;
    }

    @Override
    public void run() {

        //乘客选择上楼/下楼
        boolean isUp;
        if (pFloor == 1) {
            isUp = true;
        } else if (pFloor == elevator.totalFloors) {
            isUp = false;
        } else {
            isUp = new Random().nextInt(2) > 0;
        }

        boolean bn = elevator.pSetUpOrDown(pFloor, isUp);
        System.err.println(pFloor + "楼乘客要" + (isUp ? "上" : "下") + "楼");

        lock.lock();
        try {
            elevatorCondition.signal();
            while (elevator.geteFloor() != pFloor) {
                personCondition.await();
            }
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        } finally {
            lock.unlock();
        }

        //乘客上电梯选择楼层
        int pTarget = new Random().nextInt(elevator.totalFloors) + 1;
        //上楼时,pTarget > floor 下楼 pTarget < floor
        if (isUp) {
            while (pTarget <= pFloor) {
                pTarget = new Random().nextInt(elevator.totalFloors) + 1;
            }
        } else {
            while (pTarget >= pFloor) {
                pTarget = new Random().nextInt(elevator.totalFloors) + 1;
            }
        }

        elevator.setPeopleNum(true);
        elevator.pClearUpOrDown(pFloor, bn);
        elevator.pSetFloor(pTarget, isUp);
        System.err.println(pFloor + "楼乘客上电梯,选择了" + pTarget + "楼,电梯现在" + elevator.getPeopleNum() + "人");

        lock.lock();
        try {
            elevatorCondition.signal();
            while (elevator.geteFloor() != pTarget) {
                personCondition.await();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

        elevator.pClearFloor(pTarget, isUp);
        elevator.setPeopleNum(false);
        System.err.println(pFloor + "楼乘客下电梯");

        lock.lock();
        try {
            elevatorCondition.signal();
        } finally {
            lock.unlock();
        }
    }
}

Core main方法类:

/**
 * @author Ironhide
 * @since 2020-11-12
 */
class Core {

    public static void main(String[] args) throws InterruptedException {

        //自定义一共10层楼
        Elevator elevator = new Elevator(10);

        ReentrantLock lock = new ReentrantLock();
        Condition personCondition = lock.newCondition();
        Condition elevatorCondition = lock.newCondition();

        //直接设置 楼层数+一电梯 个线程
        ExecutorService threadPool = Executors.newFixedThreadPool(elevator.totalFloors + 1);
        threadPool.execute(new ElevatorThread(elevator, lock, personCondition, elevatorCondition));

        //可以在此测试线程
        for (int i = 1; i <= elevator.totalFloors; i++) {
            TimeUnit.SECONDS.sleep(new Random().nextInt(1) + 1);
            threadPool.execute(new FloorThread(i, elevator, lock, personCondition, elevatorCondition));
        }
    }
}

输出结果:

电梯无目标…
1楼乘客要上楼
1楼乘客上电梯,选择了2楼,电梯现在1人
电梯方向为上,目标为2楼,当前在2楼
2楼乘客要上楼
1楼乘客下电梯
2楼乘客上电梯,选择了8楼,电梯现在1人
电梯方向为上,目标为8楼,当前在3楼
3楼乘客要下楼
电梯方向为上,目标为8楼,当前在4楼
4楼乘客要下楼
电梯方向为上,目标为8楼,当前在5楼
5楼乘客要下楼
电梯方向为上,目标为8楼,当前在6楼
6楼乘客要下楼
电梯方向为上,目标为8楼,当前在7楼
7楼乘客要下楼
电梯方向为上,目标为8楼,当前在8楼
2楼乘客下电梯
8楼乘客要下楼
电梯方向为下,目标为7楼,当前在7楼
7楼乘客上电梯,选择了3楼,电梯现在1人
9楼乘客要下楼
电梯方向为下,目标为6楼,当前在6楼
6楼乘客上电梯,选择了2楼,电梯现在2人
10楼乘客要下楼
电梯方向为下,目标为5楼,当前在5楼
5楼乘客上电梯,选择了2楼,电梯现在3人
电梯方向为下,目标为4楼,当前在4楼
4楼乘客上电梯,选择了2楼,电梯现在4人
电梯方向为下,目标为3楼,当前在3楼
7楼乘客下电梯
3楼乘客上电梯,选择了1楼,电梯现在4人
电梯方向为下,目标为2楼,当前在2楼
4楼乘客下电梯
6楼乘客下电梯
5楼乘客下电梯
电梯方向为下,目标为1楼,当前在1楼
3楼乘客下电梯
电梯方向为上,目标为9楼,当前在2楼
电梯方向为上,目标为9楼,当前在3楼
电梯方向为上,目标为9楼,当前在4楼
电梯方向为上,目标为9楼,当前在5楼
电梯方向为上,目标为9楼,当前在6楼
电梯方向为上,目标为9楼,当前在7楼
电梯方向为上,目标为9楼,当前在8楼
电梯方向为上,目标为9楼,当前在9楼
9楼乘客上电梯,选择了6楼,电梯现在1人
电梯方向为上,目标为10楼,当前在10楼
10楼乘客上电梯,选择了2楼,电梯现在2人
电梯方向为下,目标为8楼,当前在9楼
电梯方向为下,目标为8楼,当前在8楼
8楼乘客上电梯,选择了2楼,电梯现在3人
电梯方向为下,目标为6楼,当前在7楼
电梯方向为下,目标为6楼,当前在6楼
9楼乘客下电梯
电梯方向为下,目标为2楼,当前在5楼
电梯方向为下,目标为2楼,当前在4楼
电梯方向为下,目标为2楼,当前在3楼
电梯方向为下,目标为2楼,当前在2楼
10楼乘客下电梯
电梯无目标…
8楼乘客下电梯
电梯无目标…

本文地址:https://blog.csdn.net/qq_38599840/article/details/109818375