可直接运行的基于Java多线程(能够同时进出车)的停车场管理系统


基本要求

  • 模拟汽车入库出库
  • 使用多线程并发操作模拟多辆车进出库
  • 停车场只有一个出口一个入口
  • 记录停车信息,基本信息:订单号、进出时间、单位时间内收费、总收费、车牌、车型,可以理解为一张收据
  • 汽车出库时讲停车信息(收据)以文本的形式写入文件,并打印在控制台
  • 能够在系统中展示所有收据

提示:以下是本篇文章正文内容,下面案例可供参考

需要实现的基本类

1.Car类:

  • 抽象类,所有车型的基类
  • 有3个String类型的字段(品牌,型号,车牌)
  • 品牌和型号由子类初始化,且不可改变,车牌由构造方法给出

2.Transaction类

  • 事务类,基本字段:事务编号(订单号)、入库时间、出库时间、收费、Car
  • 实现序列化接口
  • 构造方法中传入订单号和Car,并以当前时间设置为入库时间
  • 拥有一个出库方法,在出库方法中以当前时间设置出库时间,并计算收费将订单号+入库时间+车辆信息+收费 以字符串的形式作为返回值返回

3.Park类

  • 停车场类,采用单例(因为只有一个停车场)
  • 模拟一个出口一个入口(一个入库方法一个出库方法)
  • 为了更好的模拟汽车出入库的停留时间,出入库时需要将该线程sleep 1秒
  • 有一个容器字段,存放Transaction对象
  • 汽车入库时容器存放一个Transactioon对象
  • 出库时从容器中去除相应的Transaction对象,并将停车信息(收据)以文本的形式写入文件,并打印在控制台
  • 系统正常退出时将容器序列化到文件(即保存当前停车状态)
  • 系统打开时从文件中反序列化得到退出时的状态(反序列化得到容器状态)

写代码前对一些问题的思考

1.什么是单例?在该系统中应该如何实现?

单例:保证整个系统中一个类只有一个对象的实例
在本系统中使用CAS实现单例,这样线程会比较安全

代码如下(示例):

public class Park implements Serializable {
    /**
     * 利用AtomicReference
     */
    private static final AtomicReference<Park> INSTANCE = new AtomicReference<Park>();

    /**
     * 私有化
     */

    /**
     * 用CAS确保线程安全
     */
    public static final Park getInstance() {
        for (; ; ) {
            Park current = INSTANCE.get();
            if (current != null) {
                return current;
            }
            current = new Park();
            if (INSTANCE.compareAndSet(null, current)) {
                return current;
            }
        }
    }

    public Park() {
        this(6);
    }

    private Park(int maxSize) {
        this.maxSize = maxSize;
    }

2.如何使用多线程模拟只有一个出口一个入口

在这里我是在Park类里面再写入两个内部类,然后再让内部类实现Runnable接口,分别重写run()方法,在run方法内分别调用不同的方法(即exit与enter方法)

代码如下(看不懂没事,看最后的源码就应该会懂了):

//出库的内部类
private class Exit implements Runnable {
    @Override
    public void run() {
        exit(exitCars);
    }
}

//入库的内部类
private class Enter implements Runnable {
    @Override
    public void run() {
        enter();
    }
}

3.怎样保证容器线程的安全?

我这里是使用CopyOnWriteArrayList来代替ArrayList来确保容器的安全,并且在运行的时候尽量去避免对车库进行操作,而是先将要入库即出库的车分别存在另外两个容器,调用出库/入库线程时再将其从车库中添加/删除

代码如下(一小部分可能无法理解用意):

    //存储进入车库的车
    private static List<Transaction> lists = new CopyOnWriteArrayList<Transaction>();
    //存储想要出库的车
    private static List<Transaction> exitCars = new CopyOnWriteArrayList<Transaction>();
    //存储想要入库的车
    private static List<Transaction> enterCars = new CopyOnWriteArrayList<Transaction>();

4.如何将对象反序列化到文件,如何从文件中反序列化?

此处是让我最烧脑的地方了,因为Transaction类里面还包含着Car类的,因此存入文件并不是那么顺利,总是碰壁,希望我的经验能够让你们少碰壁,在java中存一个类容易,但是如果这个类里面还包含着类,那就有点小麻烦了(过两天再编辑一下,告诉你们如何处理,也可以先看源码自行理解)


整体思路分析及源码

提示:整体思路分析由于本人时间并不充足,因此在源码内添加了注释,应该都还是很好理解的,等我过段时间有空了会一起将序列化与反序列化遇到的问题以及源码分析一起分析给大家。(建议大家可以将源码复制到编译器中,这样便于理解,也可以自行debug分析)

源码:

(记得自行将包名修改一下)
Car类:

package BigTest;

import java.io.Serializable;

/*
抽象类,所有车型的基类
有3个String类型的字段(品牌,型号,车牌)
品牌和型号又子类初始化,且不可改变,车牌由构造方法给出
要求重写以下3个方法
blic int hashCode() {} public boolean equals(Object obj) {} public String toString() {}
*/
public abstract class Car implements Serializable {
    private String number;
    private final String brand = initBrand();//最终属性不能够修改
    private final String model = initModel();

    Car() {
    }

    protected abstract String initBrand();

    protected abstract String initModel();

    public Car(String number) {
        this.number = number;
    }

    public String getNumber() {
        return number;
    }

    public String getBrand() {
        return brand;
    }

    public String getModel() {
        return model;
    }

    @Override
    public int hashCode() {//判断两个对象是否相同
        int result;
        result = this.getNumber().hashCode() + this.getBrand().hashCode() + this.getModel().hashCode();
        return result;
    }

    @Override
    public boolean equals(Object obj) {//一段时间内,一辆车在出来之前,只能进入一次停车场,判断输入的车辆信息是否有重复,有重复的话只输入一次
        if (obj.hashCode() == this.hashCode())
            return true;
        else
            return false;
    }

    @Override

    public String toString() {//将车辆信息返回成一个字符串,包括车牌号,车型,商标。
        String abc = "车牌号:" + this.getNumber() + " 车型:" + this.getModel() + " 商标:" + this.getBrand();
        return abc;
    }

    public void printinformation() {
        String abc = this.toString();
        System.out.println(abc);
    }
}

DerviedCar类(实现Car接口的类):

package BigTest;

public class DerivedCar extends Car {
    DerivedCar(){}
    @Override
    protected String initBrand() { return "宝马"; }
    @Override
    protected String initModel() { return "轿车"; }
    public DerivedCar(String number) {
        super(number);
    }
}

DerviedCar1类(实现Car接口的类):

package BigTest;

public class DerivedCar1 extends  Car {
    DerivedCar1(){}
    @Override
    protected String initBrand() {
        return "奔驰";
    }
    @Override
    protected String initModel() {
        return "轿车";
    }
    public DerivedCar1(String number) { super(number); }
}

DerviedCar2类(实现Car接口的类):

package BigTest;

public class DerivedCar2 extends Car {
    DerivedCar2(){}
    @Override
    protected String initBrand() {
        return "比亚迪";
    }
    @Override
    protected String initModel() { return "SUV"; }
    public DerivedCar2(String number) {
        super(number);
    }
}

Transaction类:

package BigTest;

import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.HashMap;

public class Transaction implements Serializable {
    //订单号:暂时使用车牌号存储,或按车辆进入的顺序生成订单号
    private String orderNumber;
    //进车库时间
    private double enterTime;
    //出车库时间
    private double exitTime;
    //用于存储格式化之后的进入车库的时间
    private String enterStime;
    //用于存储格式化之后的退出车库的时间
    private String exitStime;

    //令一秒钟0.0025元,即一分钟0.15元,一小时9元(不足一小时按一小时算)
    //使用出库时间-出库时间算出毫秒的时间差,(时间差/1000)/3600即可得出小时差
    private int numCost;
    private Car car;

    //获取订单信息
    public String getOrderNumber() {
        return orderNumber;
    }

    //获取订单信息内的Car类
    public Car getCar() {
        return car;
    }

    public double getEnterTime() {
        return enterTime;
    }

    public double getExitTime() {
        return exitTime;
    }

    public String getEnterStime() {
        return enterStime;
    }

    public String getExitStime() {
        return exitStime;
    }

    public int getNumCost() {
        return numCost;
    }

    public void setOrderNumber(String orderNumber) {
        this.orderNumber = orderNumber;
    }

    private static HashMap<Integer, String> sum = new HashMap<>();

    //使用多态传入Car类型的车辆
    Transaction(String orderNumber,Car car){
        this.orderNumber = orderNumber;
        this.car = car;
        enterTime = System.currentTimeMillis();
        //格式化时间
        SimpleDateFormat formatter= new SimpleDateFormat("yyyy-MM-dd 'at' HH:mm:ss z");
        enterStime = formatter.format(enterTime);
    }

    public String exitWay(){
        //获取当前时间
        exitTime = System.currentTimeMillis();
        //格式化时间
        SimpleDateFormat formatter= new SimpleDateFormat("yyyy-MM-dd 'at' HH:mm:ss z");
        exitStime = formatter.format(exitTime);
        //总收费
        numCost =(int) ((((exitTime - enterTime)/1000.0)/3600.0)*9.0);
        String flag = ("订单号:" + orderNumber + " 入库时间:" + enterStime + " 出库时间:" + exitStime + " " + car.toString() + " 总花费:" + numCost);
        return flag;
    }

    public String toString(){
        String flag = ("订单号:" + orderNumber + " 入库时间:" + enterStime + " 出库时间:" + exitStime + " " + car.toString() + " 总花费:" + numCost);
        return flag;
    }

    //判断汽车是否符合要求(判断是否是系统已存在的车型及品牌)
    public static Car Judging(String model,String brand,String orderNumber){
        sum.put(0,"宝马轿车");
        sum.put(1,"奔驰轿车");
        sum.put(2,"比亚迪SUV");
        int j = 0;
        String judg = brand+model;
        //遍历HashMap
        for(int i: sum.keySet()){
            if(sum.get(i).equals(judg)){
                j = i;
                break;
            }else{
                j = -1;
            }
        }
        Car c1;
        //根据输出的值来创建car
        if(j == 0 ){
            c1 = new DerivedCar(orderNumber);
        }else if(j == 1){
            c1 = new DerivedCar1(orderNumber);
        }else if(j == 2){
            c1 = new DerivedCar2(orderNumber);
        }else{
            c1 = null;
        }

        return c1;
    }

}

Park类:

package BigTest;

import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicReference;

public class Park implements Serializable {
    /**
     * 利用AtomicReference
     */
    private static final AtomicReference<Park> INSTANCE = new AtomicReference<Park>();

    /**
     * 私有化
     */

    /**
     * 用CAS确保线程安全
     */
    public static final Park getInstance() {
        for (; ; ) {
            Park current = INSTANCE.get();
            if (current != null) {
                return current;
            }
            current = new Park();
            if (INSTANCE.compareAndSet(null, current)) {
                return current;
            }
        }
    }

    public Park() {
        this(6);
    }

    private Park(int maxSize) {
        this.maxSize = maxSize;
    }

    //表示停车场的车辆个数
    private static int maxSize;
    //表示当前停车场占用的位置
    private static int top = 0;
    //默认初始化的订单号(如果有文件存储订单号的话,则会使用文件存储的订单号,继续累加下去)
    private static int order = 1;


    //内部类使用Runnable接口,使用两个内部类就可以模拟出一个入口一个出口了
    //这样在入口进车的同时还可以让车子出库。
    //出库的内部类
    private class Exit implements Runnable {
        @Override
        public void run() {
            exit(exitCars);
        }
    }

    //入库的内部类
    private class Enter implements Runnable {
        @Override
        public void run() {
            enter();
        }
    }

    //使用CopyOnWriteArrayList来确保容器线程的安全,同时使出库的车与入库的车分开存放,在出入库操作时尽量避免同时而操作造成越界
    //存储进入车库的车
    private static List<Transaction> lists = new CopyOnWriteArrayList<Transaction>();
    //存储想要出库的车
    private static List<Transaction> exitCars = new CopyOnWriteArrayList<Transaction>();
    //存储想要入库的车
    private static List<Transaction> enterCars = new CopyOnWriteArrayList<Transaction>();


    //暂时存储用户需要入库的车,待用户总体输入完毕之后,便执行多线程
    private void enter(Car car) {
        //获取当前订单号
        String orderNumber = String.valueOf(order);
        //主菜单中已判定传入的car不为null了,因此可以直接添加
        enterCars.add(new Transaction(orderNumber, car));
        order++;
    }

    //此为出库线程run时执行的方法
    private void enter() {
        //遍历enterCars容器,获取要出库的车的信息
        for (Transaction transaction : enterCars) {
            if (top == maxSize) {
                System.out.println("车位不足" + "车牌号为:" + transaction.getOrderNumber() + "入库失败失败");
            } else {
                boolean flag = true;
                //遍历车库内的信息,判断是否已存在该车牌
                for (Transaction transaction1 : lists) {
                    if (transaction1.getCar().getNumber().equals(transaction.getCar().getNumber())) {
                        System.out.println(transaction.getOrderNumber() + "车牌已经存在,汽车入库失败");
                        flag = false;
                        break;
                    }
                }
                //如果车库内不存在该车牌信息则执行以下操作
                if (flag) {
                    //将该车辆信息存入车库内(lists存放入库的车)
                    lists.add(transaction);
                    //从候车区删除已经入库的车
                    enterCars.remove(transaction);
                    //设定入库时间为1s
                    try {
                        Thread.currentThread().sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(transaction.getCar().getNumber() + "入库成功");
                    top += 1;
                }
            }
        }
    }

    //为菜单提供一个方法供菜单使用
    private void enter(Car car, Park p1) {
        p1.enter(car);
    }

    //此方法为多线程调用的出库方法
    private void exit(List<Transaction> exitCars) {
        for (Transaction transaction : exitCars) {
            System.out.println(transaction.getCar().getNumber() + "出库成功");
            System.out.println(transaction.exitWay());
            try {
                //出库之后就将出库的信息序列化
                SerializableTest.serializecar(transaction);
            } catch (IOException e) {
                e.printStackTrace();
            }
            //将其出库候车区移除
            exitCars.remove(transaction);
            try {
                Thread.currentThread().sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    //用于用户暂时存储要出库的车,等用户输入完毕后调用线程再执行出库
    private void exit(String... orderNumbers) {
        for (String orderNumber : orderNumbers) {
            for (Transaction transaction : lists) {
                //如果车牌号存在,则将其加入候车区,等待线程执行出库操作
                if (orderNumber.equals(transaction.getCar().getNumber())) {
                    exitCars.add(transaction);
                    //从车库中移除该信息
                    lists.remove(transaction);
                    top--;
                    break;
                }
            }
        }
    }


    //打印菜单
    public void Menu() {
        int chose = -2;
        //对车库进行初始化
        //***********提取文件内已存车的信息************
        ArrayList arr = new ArrayList();
        try {
            arr = SerializableTest.deserializecar1();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("车库内已有:" + arr.size() + "辆车");
        //将提取出来的Object类转换成Transaction类,这样才能够进行出库操作
        for (int j = 0; j < arr.size(); j++) {
            Object ok = (arr.get(j));
            Transaction ok1 = (Transaction) ok;
            lists.add(ok1);
            top++;
            System.out.println(ok1.getOrderNumber());
        }
        //******************************************


        //*************提取订单号********************
        //便于系统根据上次关闭前的订单号序列进行订单号的编写
        try {
            order = SerializableTest.deserializecar1int();
        } catch (Exception e) {
            e.printStackTrace();
        }
        //******************************************

        //当chose=-1时才退出系统
        while (chose != -1) {
            System.out.println("*******欢迎进入停车场管理系统*******");
            System.out.println("请选择服务");
            System.out.println("1.停车");
            System.out.println("2.查看停车时长");
            System.out.println("3.已停车辆信息");
            System.out.println("4.查看剩余停车位");
            System.out.println("5.离开并缴费");
            System.out.println("6.查看已出库订单信息");
            System.out.println("0.关闭系统");
            chose = testInput();
            switch (chose) {
                case 0:
                    //**********以下为退出系统之前需要进行的操作***********
                    if (chose == 0) {
                        //①将系统关闭之前还未出库的车的信息存入文件内
                        ArrayList arr1 = new ArrayList();
                        for (Transaction transaction : lists) {
                            arr1.add(transaction);
                        }
                        try {
                            SerializableTest.serializecar1(arr1);
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                    //②释放系统中lists的空间
                    lists.clear();
                    //③将系统关闭前最后一个订单序号存入文件中中
                    try {
                        SerializableTest.serializecarint(order);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    //*****************************************8
                    chose = -1;
                    break;
                case 1:
                    //制作一辆车
                    Car car = makeCar();
                    while (car == null) {
                        System.out.println("车辆信息不存在,请重新输入");
                        car = makeCar();
                    }
                    //使车入库
                    enter(car, this);
                    //新建一个调用Park内部类(Enter类)线程的匿名类
                    new Thread(this.new Enter()).start();
                    break;
                case 2:
                    //提示用户输入车票号
                    String Number = Number();
                    //根据车牌号获取已停时间
                    placeTime(Number);
                    break;
                case 3:
                    //获取所有在车库内的信息
                    searchAll();
                    break;
                case 4:
                    //获取当前车位
                    placeNumber();
                    break;
                case 5:
                    //汽车出库
                    exit();
                    //新建一个调用Park内部类(Exit类)线程的匿名类
                    new Thread(this.new Exit()).start();
                    break;
                case 6:
                    //获取订单文件内的所有信息
                    readFile();
                    break;
                default:
                    System.out.println("我无法理解您的需求,请重新输入~");
                    break;
            }
        }
    }

    //根据用户的输入来获取一辆车
    private Car makeCar() {
        String model;
        String brand;
        String orderNumber;
        Scanner sc = new Scanner(System.in);
        System.out.print("请输入你的车型:");
        model = sc.next();
        System.out.print("请输入车的品牌:");
        brand = sc.next();
        System.out.print("请输入车牌号:");
        orderNumber = sc.next();
        //判断该车是否符合要求(即车型与品牌是有效的)
        return Transaction.Judging(model, brand, orderNumber);
    }

    //获取车牌号
    private String Number() {
        System.out.println("请输入车牌号");
        Scanner sc = new Scanner(System.in);
        String orderNumber = sc.next();
        return orderNumber;
    }
    //根据车牌号搜寻车辆停的时间
    private int placeTime(String Number) {
        int time = -1;
        for (Transaction transaction : lists) {
            if (Number.equals(transaction.getCar().getNumber())) {
                //找到该订单号之后获取当前时间
                long nowTime = System.currentTimeMillis();
                time = (int) (((nowTime - transaction.getEnterTime()) / 1000.0) / 3600);
                break;
            }
        }
        if (time != -1) {
            System.out.println("车牌号为:" + Number + "的车已停时长为" + time + "小时");
        } else {
            System.out.println("您输入的车牌号不存在");
        }
        return time;
    }

    //获取车库中的车位
    private void placeNumber() {
        System.out.println("目前还有" + (maxSize - top) + "个车位");
    }

    //给菜单提供一个退出的方法
    private void exit() {
        System.out.println("请输入要出库的车牌号(一次最多20辆车进入候车区,若输入为0则结束出库)");
        Scanner sc = new Scanner(System.in);
        int flag = 0;
        //最多一次出库20辆车
        String[] orderNumbers = new String[20];
        String orderNumber;
        orderNumber = sc.next();
        while (!orderNumber.equals("0")) {
            orderNumbers[flag] = orderNumber;
            flag++;
            orderNumber = sc.next();
        }
        //信息传给另一个数组,避免原数组后面null值进行exit操作
        String[] orderNumbers1 = new String[flag];
        for (int i = 0; i < flag; i++) {
            orderNumbers1[i] = orderNumbers[i];
        }
        exit(orderNumbers1);
    }

    //获取车库内车库的信息
    private void searchAll() {
        for (Transaction transaction : lists) {
            String flag = ("订单号:" + transaction.getOrderNumber() + " 入库时间:" + transaction.getEnterStime() + transaction.getCar().toString());
            System.out.println(flag);
        }
    }

    //用于判定用户输入的内容是否合法(int类型的)
    private int testInput() {
        Scanner sc = new Scanner(System.in);
        String input = sc.next();
        if (input == null || input.length() == 0) {
            return -3;
        }

        int number = 0;
        int mark = 0; //帮助循环标记第一位
        int var = 0; //表示正负
        char[] chars = input.toCharArray();


        //判断正负数
        //if(chars[0] == "-")  这一段可以放在后面,但是为了方便循环
        if (chars[0] == '-' || chars[0] == '+') {
            mark = 1;
            if (chars[0] == '-')
                var = 1;
        }
        //此处i不能单纯的从0开始,要么对第一位的+-做判断,要么在前面就解决
        for (int i = mark; i < input.length(); i++) {

            if (chars[i] < 48 || chars[i] > 57) {
                return -3;
            }
            number = number * 10 + chars[i] - 48;  //注意与真正数字切换
        }
        return var == 1 ? -number : number;
    }

    //读取文本(也就是已经出库的订单信息)
    private static void readFile() {
        String pathname = "d:/hell.txt";
        try (FileReader reader = new FileReader(pathname);
             BufferedReader br = new BufferedReader(reader) // 建立一个对象,它把文件内容转成计算机能读懂的语言
        ) {
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //内部类
    public static class SerializableTest {//用于出库的车的账单的查询
        //传入需要写入文件的订单信息(参数为Transaction类)
        private static void serializecar(Transaction m) throws IOException {
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("d:/hell.txt"), true));
            oos.writeObject(m.exitWay() + "\n");
            oos.close();
        }

        //将车库内的车写入文件,避免系统关闭之后信息丢失
        private static void serializecar1(Object object) throws IOException {//传入需要写入文件的transaction 类
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("d:/hell12.txt")));
            oos.writeObject(object);
            oos.close();
            oos.flush();
        }

        //读取上次系统关闭之后还未出库的汽车
        private static ArrayList deserializecar1() throws Exception {
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("d:/hell12.txt")));
            ArrayList arr1 = new ArrayList();
            arr1 = (ArrayList) ois.readObject();
            return arr1;
        }

        //获取订单号
        private static void serializecarint(int m) throws IOException {//传入int
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("d:/int.txt")));
            oos.writeObject(m);
            oos.close();
        }

        //保存订单号
        private static int deserializecar1int() throws Exception {
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("d:/int.txt")));
            int arr1;
            arr1 = (int) ois.readObject();
            return arr1;
        }
    }
}

programRun类(测试类):

package BigTest;

public class programRun {
    public static void main(String[] args) {
        //创建一个Park的实例
        Park p1 = Park.getInstance();
        //打印菜单
        p1.Menu();
    }
}

运行示例:

停车:
在这里插入图片描述
查看车信息:
在这里插入图片描述出库并缴费:
在这里插入图片描述查看所有出库的车的信息(不知道什么原因,开头都是莫名其妙的乱码,但是我的信息是完整的):
在这里插入图片描述


总结

将代码复制到编译器中,修改一下包名即可测试并使用,有错误或者说错的地方欢迎大家指正,出库与入库都是使用的内部类的线程,所以你们会发现有时候主线程已经打印了菜单而分线程才执行完出库/入库/打印订单的操作。
(如果觉得本篇文章对你用处较大,请点个赞再走呗~)

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 1024 设计师:白松林 返回首页