엘리베이터 시뮬레이션 만들기
목차
- 엘리베이터 시뮬레이션 알고리즘
- 엘리베이터 시뮬레이션 객체 설계
- 엘리베이터 구현
- 엘리베이터 쓰레드 설계
- 결과
1. 엘리베이터 시뮬레이션 알고리즘
while(사람이 모두 이용할때까지){
1. 현재층에서 내릴 사람이 있는지 엘리베이터 내부를 확인한다.
2. 현재층에서 탑승할 사람이 있는지 확인한다.(탑승시에 중량을 확인, 올라가는지 혹은 내려가는지 확인 후 목적에 따라 탑승)
3.
1. 탑승한 사람이 있다면 목적지 설정
2. 탑승한 사람이 없다면, 건물에 엘리베이터 기다리는 사람을 탐색 후, 목적지 설정(사람이 있는 층으로)
4. 설정된 목적지가 있다면 목적지로 출발합니다. (층마다 알고리즘 1,2 를 실행.)
}
내가 엘리베이터를 이용하였을 때 생각나는 순서도를 기준으로 알고리즘을 구상해 보았다.
엘리베이터가 층을 이동할 때마다 각 층에서 사람을 태우거나 내려주고 목적지를 설정하고 해당 층으로 이동한다.
추가적으로 엘리베이터에 이동 방향(위로가는지, 아래로가는지)를 적용해서 사용자가 이동하는 방향과 같을 때만
엘리베이터를 타도록 했다. (실제로 올리갈려고할 때 내려가는 엘리베이터를 타지는 않으니까!)
2. 엘리베이터 시뮬레이션 객체 설계
Building
건물을 의미
건물은 엘리베이터 2개를 가지고 있다 has a 관계
건물은 사람들을 가지고 있다. has a 관계
Elevator
엘리베이터를 의미
엘리베이터에는 탑승하는 사람들이 있기에 엘리베이터는 사람들을 가지고 있다 has a 관계
User
사람을 의미
3. 엘리베이터 구현
Building.class
public class Building {
private static final Long BuildingHeight = 5L;
//엘리베이터를 완료하고 도착한 사람들
public static List<User> result = new ArrayList<>();
//대기하는 사람
public static List<User> waitUserList = new ArrayList<>();
Elevator elevator = new Elevator();
public static void main(String[] args) throws InterruptedException {
setting();
elevator.startElevator();
}
private static void setting() {
Random rand = new Random();
for (int i = 0; i < 5; i++) {
User user = new User(
Long.valueOf(rand.nextInt(4) + 2),
Long.valueOf(rand.nextInt(50) + 40),
"User" + i
);
Long startFloor;
while(true){
startFloor = Long.valueOf(rand.nextInt(5) + 1);
//startFloor = 1L;
if(!user.getDestination().equals(startFloor))
break;
}
user.setStartFloor(startFloor);
waitUserList.add(user);
}
for (User user : waitUserList) {
System.out.println("=====대기명단======");
System.out.println("이름 = " + user.getName());
System.out.println("무게 = " + user.getWeight());
System.out.println("시작 층 = " + user.getStartFloor());
System.out.println("목적지 층 = " + user.getDestination());
}
}
}
연관관계에 맞추어 필드 구성을 해주었다.
엘리베이터를 기다리는 사람, 도착한 사람 모두 건물에 있기에 builiding.class에서 정의를 해주었다.
세팅은 랜덤으로 시작 층과 목적지 층 그리고 무게를 랜덤으로 설정해 주었다.
User.class
public class User {
//목적지
private Long destination;
//시작 층
private Long startFloor;
//탑승 시각
private Long takeInTime;
//하선 시간
private Long takeOffTime;
// 사람 무게
private Long weight;
//이름
private String name;
public User(Long destination, Long weight, String name) {
this.destination = destination;
this.weight = weight;
this.name = name;
}
public Long getDestination() {
return destination;
}
public Long getStartFloor() {
return startFloor;
}
public Long getWeight() {
return weight;
}
public String getName() {
return name;
}
public void setDestination(Long destination) {
this.destination = destination;
}
public void setStartFloor(Long startFloor) {
this.startFloor = startFloor;
}
public void setTakeInTime(Long takeInTime) {
this.takeInTime = takeInTime;
}
public void setTakeOffTime(Long takeOffTime) {
this.takeOffTime = takeOffTime;
}
public Long getTakeInTime() {
return takeInTime;
}
public Long getTakeOffTime() {
return takeOffTime;
}
public void setWeight(Long weight) {
this.weight = weight;
}
public void setName(String name) {
this.name = name;
}
}
유저 클래스는 유저가 필요로하는 속성을 만을 정의해주었다.
Elevator.class
public class Elevator {
//탑승하고 있는 유저
private List<User> takeInUserList = new ArrayList<>();
//현재 엘리베이터 무게 default = 0
private Long currentWeight = 0L;
// 현재 엘리베이터 위치 default = 1(층)
private Long currentFloor = 1L;
// 엘리베이터의 최종 목적지
private Long destination;
// 엘리베이터 상태, STOP, UP_FLOOR, DOWN_FLOOR;
private ElevatorState elevatorState = ElevatorState.STOP;
// 엘리베이터의 문 상태
private DoorStatus doorStatus = DoorStatus.CLOSE;
// 엘리베이터의 속도
private final Long speed = 1L;
// 최대 무게
private final Long maxWeight = 1000L;
// 엘리베이터 작동 시간
private Long operationTime = 0L;
엘리베이터 클래스는 조금 길기에 나누어서 작성해보겠다.
일단은 엘리베이터가 가져야할 필드들을 정의해 두었다. 엘리베이터의 상태와 문 상태는 Enum으로 처리하였으다.
//엘리베이터 작동 시작
public void startElevator() throws InterruptedException {
int userWait = Building.waitUserList.size();
//탑승할 유저가 없을때까지 작동
while(userWait != Building.result.size()){
//탑승하고 있는 유저, 목적지에 맞다면 내리기
takeOffElevator();
//해당 층에서 기다리는 유저가 있다면 탑승하기
takeInElevator();
//엘리베이터에 탑승 고객이 있을 시 탑승 고객에 맞춘 목적지 선택
if(takeInUserList.size() != 0){
destinationSet();
}
//탑승 인원이 없다면 다른 층의 탑승 고객 찾기
else {
destiantionSetIfNotInUser();
}
goDestination();
}
}
대망의 엘리베이터 작동 함수 이다.
모든 유저가 목적지에 도달할 때 까지 엘리베이터를 운영한다.
알고리즘 번호 대로 함수를 잘라서 구현하였으다.
takeOffElevator()
public synchronized void takeOffElevator() throws InterruptedException {
for (int i = 0; i< takeInUserList.size(); i++) {
User user = takeInUserList.get(i);
if(user.getDestination().equals(currentFloor) ){
doorStatus = DoorStatus.OPEN;
user.setTakeOffTime(operationTime);
currentWeight -= user.getWeight();
takeInUserList.remove(user);
Building.result.add(user);
i--;
}
}
doorStatus = DoorStatus.CLOSE;
}
일단 모든 엘리베이터 사용자의 목적지 층과 엘리베이터의 현재 층 중 같은 사용자를 찾게 된다.
만약 해당 층에 내리는 사용자가 있다면 사용자가 지금까지 탄 시간을 저장하고
엘리베이터 문을 열고 사용자를 엘리베이터에서 내보낸다. (takeInUserList.remove(user))
자연스럽게 현재 엘리베이터의 무게는 줄게 되고 (currentWeight -= user.getWeight())
사용자는 빌딩으로 가게 된다. (Building.result.add(user)
마지막으로 문을 닫는다.
takeInElevator()
public synchronized void takeInElevator() throws InterruptedException {
for (int i = 0; i< Building.waitUserList.size(); i++) {
if(maxWeight < currentWeight)
break;
User mainUser = Building.waitUserList.get(i);
ElevatorState userState
= mainUser.getDestination() - currentFloor > 0 ? ElevatorState.UP_FLOOR : ElevatorState.DOWN_FLOOR;
if(mainUser.getStartFloor().equals(currentFloor) && (userState.equals(elevatorState) || elevatorState.equals(ElevatorState.STOP))){
if(User.takeInElevator(mainUser, operationTime)){
doorStatus = DoorStatus.OPEN;
takeInUserList.add(mainUser);
currentWeight += mainUser.getWeight();
Building.waitUserList.remove(mainUser);
}
else
break;
printStatus(mainUser, "======사용자 탑승", "탑승 목적지 = ", mainUser.getStartFloor(), "하차 목적지 = ", mainUser.getDestination(), "현재 목적지 = ", currentFloor);
i = i -1;
}
}
doorStatus = DoorStatus.CLOSE;
}
일단 엘리베이터를 기다리는 사용자들 중에서 (for i ~ buidling.waitUserList~~)
(무게 제한이 걸리면 바로 break (if(maxWeight<currentWeight) break))
사용자의 가는 방향을 구하게 된다. (ainUser.getDestination() - currentFloor > 0 ? ElevatorState.UP_FLOOR : ElevatorState.DOWN_FLOOR;)
만약에 사용자가 엘리베이터의 현재 위치에 있고,
엘리베이터의 가는 방향이 같거나 엘리베이터의 가는 방향이 없다면 엘리베이터를 타게된다.
탔을 때는 문을 열고,
엘리베이터 객체의 이용고객 리스트를 뜻하는 takeInUserLIst에 추가되고, Building.waitUserLIst에서 삭제시킨다.
destinationSet();
private void destinationSet() {
Long[] tmpArr = new Long[takeInUserList.size()];
for(int q = 0 ; q < tmpArr.length ; q++)
tmpArr[q] = takeInUserList.get(q).getDestination();
Arrays.sort(tmpArr);
//목적지 정하기
if(this.elevatorState == ElevatorState.UP_FLOOR)
this.destination = tmpArr[0];
else
this.destination = tmpArr[tmpArr.length-1];
//목적지에 따라 위로 갈지 아래로 갈지 정하기
if(destination > currentFloor)
elevatorState = ElevatorState.UP_FLOOR;
else
elevatorState = ElevatorState.DOWN_FLOOR;
}
이제 엘리베이터의 탑승자도 얻었다면
그 탑승자 중에서 목적지 층과 방향을 알아내야 한다.
탑승자 들 중에서 가장 가까운 곳으로 목적지 층을 지정하고
목적지 층에 따라서 엘리베이터의 가는 방향을 설정한다.
destiantionSetIfNotInUser()
private void destiantionSetIfNotInUser() {
if(Building.waitUserList.size()!= 0){
destination = Building.waitUserList.get(0).getStartFloor();
elevatorState = destination - currentFloor > 0 ? ElevatorState.UP_FLOOR : ElevatorState.DOWN_FLOOR ;
}
}
엘리베이터의 탑승저가 없다면, 탑승자를 탐색하고 목적지 층과 방향을 설정한다.
goDestination()
while(currentFloor != destination){
if(this.elevatorState.equals(ElevatorState.UP_FLOOR)){
operationTime += 1L;
currentFloor += speed;
}
else if(this.elevatorState.equals(ElevatorState.DOWN_FLOOR)){
operationTime += 1L;
currentFloor -= speed;
}
//매 층마다 탈사람 내릴사람 보내기
takeOffElevator();
takeInElevator();
}
//도착했으면 stop 모드로 바꾸기
elevatorState = ElevatorState.STOP;
이제 목적지 층 과 방향이 정해졌으면 엘리베이터를 가동한다.
목적지 층에 도달할 때까지 방향에 따라 1층씩 이동한다.
1층 씩 오를 때마다 각 층에 탈 사람과 내릴 사람이 있는지 확인한다.
목적지 층에 도착했다면 엘리베이터 상태를 STOP으로 바꾼다.
4. 엘리베이터 쓰레드 설계
추가적으로 멀티쓰레드 기능도 넣었다.
각 엘리베이터마다 하나의 쓰레드로 실행하여 멀티 쓰레드를 실행할 수 있게한다.
하나의 쓰레드에서는 한 개의 엘리베이터 객체를 사용한다. (멀티 쓰레드라도 공통된 객체를 사용할 수 있기에 주의해야한다.)
MyThread.class
class MyThread implements Runnable{
Elevator elevator;
//쓰레드 각각의 엘리베이터 객체 생성
public MyThread() {
this.elevator = new Elevator();
}
@Override
public void run() {
try {
elevator.startElevator();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
하나의 쓰레드에서 하나의 엘리베이터 객체를 사용하기 위해서
MyThread 라는 쓰레드를 만들고 그 안에 새로운 엘리베이터 객체를 사용할 수 있게 한다.
Building.class
public static void main(String[] args) throws InterruptedException {
setting();
//쓰레드 실행
for(int i = 0; i < 2; i ++) {
new Thread(new MyThread()).start();
}
}
빌딩에서의 엘리베이터 가동 또한 쓰레드로 한번 감싸서 실행한다.
Building.waitUser 리스트에 락 걸기
멀티 쓰레드를 사용할 때 가장 주의해야 할 점이 바로 공통으로 사용하는 메모리에 락을 거는 것이다.
잘못하면 데드락이 걸려서 프로그래밍이 꼬일 수 있다.
그렇기에 두 개의 엘리베이터가 공통으로 사용하는 Building.waitUser에 락을 건다.
Building.java
//대기하는 사람들
public static List<User> waitUserList = new ArrayList<>();
public static synchronized boolean takeInElevator(User user, Long startTime) {
if (Building.waitUserList.contains(user)) {
user.setTakeInTime(startTime);
Building.waitUserList.remove(user);
return true;
}
else
return false;
}
Elevator.java
if(Building.takeInElevator(mainUser, operationTime)){
doorStatus = DoorStatus.OPEN;
takeInUserList.add(mainUser);
currentWeight += mainUser.getWeight();
}
엘리베이터를 기다린 사용자들을 삭제하기 로직에 락을 건다. 그 후에 사용자가 존재하면 정상적으로 삭제,
없다면 false를 리턴하여 엘리베이터에 if문을 사용하지 않도록 한다.
5. 결과
전체 소스코드
https://github.com/salmon2/Elevator-Simulation
GitHub - salmon2/Elevator-Simulation
Contribute to salmon2/Elevator-Simulation development by creating an account on GitHub.
github.com