조건
화장실의 칸은 n개 이며, m명의 사람들이 급한 볼 일을 보기 위해 대기한다.
이를 이용하는 사람마다 이용 시간은 최소 1분에서 10분까지 걸린다.
화장실을 대기하는 사람들을 공평하고 안전하게 볼 일을 보고 나갈 수 있도록 시뮬레이터를 작성
- 처리 조건
- 화장실 칸 수는 실행 전에 인수로 받도록 하고 각 화장실을 스레드로 구현하도록 한다.
- 대기 하는 사람은 실행전 인수로 받도록 하고 랜덤 함수에 의하여 생성하도록 한다.
- 실행을 빨리 돌려야 하므로 1분을 1초로 환산하여 처리하도록 한다.
- 아래의 예와 같이 1분 단위로 상태 변화를 출력하여 스케줄링 상태를 확인할 수 있도록 한다.
최초 대기열 : [ 1 ( 7 / 7 ) ] [ 2 ( 10 / 10 ) ] [ 3 ( 8 / 8 ) ] [ 4 ( 9 / 9 ) ] [ 5 ( 7 / 7 ) ] [ 6 ( 6 / 6 ) ] [ 7 ( 5 / 5 ) ] [ 8 ( 1 / 1 ) ] [ 9 ( 10 / 10 ) ] [ 10 ( 3 / 3 ) ]
>> 화장실 모니터 : [ 0 초 경과 ] ==================================================================================
대기열 : [ 6 ( 6 / 6 ) ] [ 7 ( 5 / 5 ) ] [ 8 ( 1 / 1 ) ] [ 9 ( 10 / 10 ) ] [ 10 ( 3 / 3 ) ]
화장실 : [ 1 ( 6 / 7 ) ] [ 3 ( 7 / 8 ) ] [ 2 ( 9 / 10 ) ] [ 4 ( 8 / 9 ) ] [ 5 ( 6 / 7 ) ]
>> 화장실 모니터 : [ 1 초 경과 ] ==================================================================================
대기열 : [ 6 ( 6 / 6 ) ] [ 7 ( 5 / 5 ) ] [ 8 ( 1 / 1 ) ] [ 9 ( 10 / 10 ) ] [ 10 ( 3 / 3 ) ]
화장실 : [ 1 ( 5 / 7 ) ] [ 3 ( 6 / 8 ) ] [ 2 ( 8 / 10 ) ] [ 4 ( 7 / 9 ) ] [ 5 ( 5 / 7 ) ]
>> 화장실 모니터 : [ 2 초 경과 ] ==================================================================================
대기열 : [ 6 ( 6 / 6 ) ] [ 7 ( 5 / 5 ) ] [ 8 ( 1 / 1 ) ] [ 9 ( 10 / 10 ) ] [ 10 ( 3 / 3 ) ]
화장실 : [ 1 ( 4 / 7 ) ] [ 3 ( 5 / 8 ) ] [ 2 ( 7 / 10 ) ] [ 4 ( 6 / 9 ) ] [ 5 ( 4 / 7 ) ]
>> 화장실 모니터 : [ 3 초 경과 ] ==================================================================================
대기열 : [ 6 ( 6 / 6 ) ] [ 7 ( 5 / 5 ) ] [ 8 ( 1 / 1 ) ] [ 9 ( 10 / 10 ) ] [ 10 ( 3 / 3 ) ]
화장실 : [ 1 ( 3 / 7 ) ] [ 3 ( 4 / 8 ) ] [ 2 ( 6 / 10 ) ] [ 4 ( 5 / 9 ) ] [ 5 ( 3 / 7 ) ]
>> 화장실 모니터 : [ 4 초 경과 ] ==================================================================================
대기열 : [ 6 ( 6 / 6 ) ] [ 7 ( 5 / 5 ) ] [ 8 ( 1 / 1 ) ] [ 9 ( 10 / 10 ) ] [ 10 ( 3 / 3 ) ]
화장실 : [ 1 ( 2 / 7 ) ] [ 3 ( 3 / 8 ) ] [ 2 ( 5 / 10 ) ] [ 4 ( 4 / 9 ) ] [ 5 ( 2 / 7 ) ]
>> 화장실 모니터 : [ 5 초 경과 ] ==================================================================================
대기열 : [ 6 ( 6 / 6 ) ] [ 7 ( 5 / 5 ) ] [ 8 ( 1 / 1 ) ] [ 9 ( 10 / 10 ) ] [ 10 ( 3 / 3 ) ]
화장실 : [ 1 ( 1 / 7 ) ] [ 3 ( 2 / 8 ) ] [ 2 ( 4 / 10 ) ] [ 4 ( 3 / 9 ) ] [ 5 ( 1 / 7 ) ]
>> 화장실 모니터 : [ 6 초 경과 ] ==================================================================================
대기열 : [ 6 ( 6 / 6 ) ] [ 7 ( 5 / 5 ) ] [ 8 ( 1 / 1 ) ] [ 9 ( 10 / 10 ) ] [ 10 ( 3 / 3 ) ]
화장실 : [ 1 ( 0 / 7 ) ] [ 3 ( 1 / 8 ) ] [ 2 ( 3 / 10 ) ] [ 4 ( 2 / 9 ) ] [ 5 ( 0 / 7 ) ]
>> 화장실 모니터 : [ 7 초 경과 ] ==================================================================================
대기열 : [ 8 ( 1 / 1 ) ] [ 9 ( 10 / 10 ) ] [ 10 ( 3 / 3 ) ]
화장실 : [ 6 ( 5 / 6 ) ] [ 3 ( 0 / 8 ) ] [ 2 ( 2 / 10 ) ] [ 4 ( 1 / 9 ) ] [ 7 ( 4 / 5 ) ]
>> 화장실 모니터 : [ 8 초 경과 ] ==================================================================================
대기열 : [ 9 ( 10 / 10 ) ] [ 10 ( 3 / 3 ) ]
화장실 : [ 6 ( 4 / 6 ) ] [ 8 ( 0 / 1 ) ] [ 2 ( 1 / 10 ) ] [ 4 ( 0 / 9 ) ] [ 7 ( 3 / 5 ) ]
>> 화장실 모니터 : [ 9 초 경과 ] ==================================================================================
대기열 :
화장실 : [ 6 ( 3 / 6 ) ] [ 9 ( 9 / 10 ) ] [ 2 ( 0 / 10 ) ] [ 10 ( 2 / 3 ) ] [ 7 ( 2 / 5 ) ]
>> 화장실 모니터 : [ 10 초 경과 ] ==================================================================================
대기열 :
화장실 : [ 6 ( 2 / 6 ) ] [ 9 ( 8 / 10 ) ] [ 10 ( 1 / 3 ) ] [ 7 ( 1 / 5 ) ]
>> 화장실 모니터 : [ 11 초 경과 ] ==================================================================================
대기열 :
화장실 : [ 6 ( 1 / 6 ) ] [ 9 ( 7 / 10 ) ] [ 10 ( 0 / 3 ) ] [ 7 ( 0 / 5 ) ]
>> 화장실 모니터 : [ 12 초 경과 ] ==================================================================================
대기열 :
화장실 : [ 6 ( 0 / 6 ) ] [ 9 ( 6 / 10 ) ]
>> 화장실 모니터 : [ 13 초 경과 ] ==================================================================================
대기열 :
화장실 : [ 9 ( 5 / 10 ) ]
>> 화장실 모니터 : [ 14 초 경과 ] ==================================================================================
대기열 :
화장실 : [ 9 ( 4 / 10 ) ]
>> 화장실 모니터 : [ 15 초 경과 ] ==================================================================================
대기열 :
화장실 : [ 9 ( 3 / 10 ) ]
>> 화장실 모니터 : [ 16 초 경과 ] ==================================================================================
대기열 :
화장실 : [ 9 ( 2 / 10 ) ]
>> 화장실 모니터 : [ 17 초 경과 ] ==================================================================================
대기열 :
화장실 : [ 9 ( 1 / 10 ) ]
>> 화장실 모니터 : [ 18 초 경과 ] ==================================================================================
대기열 :
화장실 : [ 9 ( 0 / 10 ) ]
>> 화장실 모니터 : [ 19 초 경과 ] ==================================================================================
대기열 :
화장실 :
먼저 화장실 이용객 이다.
class Person {
private final int id;
private final int time;
private int remainingTime;
public Person(int id, int time) {
this.id = id;
this.time = time;
this.remainingTime = time;
}
public int getId() {
return id;
}
public int getTime() {
return time;
}
public void setRemainingTime(int remainingTime) {
this.remainingTime = remainingTime;
}
public int getRemainingTime() {
return remainingTime;
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", time=" + time +
", remainingTime=" + remainingTime +
'}';
}
}
화장실 이용객의 번호와 부여된 시간, 남은 시간이다.
class RestRoomSchedule {
public static final int MAX_WAITING = 100;
private final int rooms;
private final BlockingQueue<Person> persons;
private final ExecutorService pool;
private final List<Future<?>> futures;
private final ConcurrentMap<Integer, Person> monitor;
private final ScheduledExecutorService scheduler;
public RestRoomSchedule(int rooms) {
this.rooms = rooms;
this.persons = new ArrayBlockingQueue<>(MAX_WAITING);
this.pool = Executors.newFixedThreadPool(rooms);
this.futures = new ArrayList<>(rooms);
this.monitor = new ConcurrentHashMap<>();
this.scheduler = Executors.newScheduledThreadPool(1);
}
public void start(int initPerson) throws InterruptedException, ExecutionException {
System.out.print("최초 대기열 : ");
for (int i = 0; i < initPerson; i++) {
Person p = new Person(i + 1, (int) (Math.random() * 10d + 1d));
persons.put(p);
System.out.printf("[ %2d ( %2d / %2d ) ] ", p.getId(), p.getRemainingTime(), p.getTime());
}
System.out.println();
this.scheduler.scheduleAtFixedRate(
new Runnable() {
private int i;
@Override
public void run() {
System.out.printf(">> 화장실 모니터 : [ %10d 초 경과 ] ==================================================================================\n", i);
System.out.print("대기열 : ");
for (Person p : persons) {
System.out.printf("[ %2d ( %2d / %2d ) ] ", p.getId(), p.getRemainingTime(), p.getTime());
}
System.out.println();
System.out.print("화장실 : ");
for (Map.Entry<Integer, Person> entry : monitor.entrySet()) {
Person person = entry.getValue();
System.out.printf("[ %2d ( %2d / %2d ) ] ", person.getId(), person.getRemainingTime(), person.getTime());
}
System.out.println();
i++;
}
},
500, 1000, TimeUnit.MILLISECONDS);
for (int i = 0; i < rooms; i++) {
futures.add(pool.submit(new RoomTask(i)));
}
for (Future<?> f : futures) {
f.get();
}
pool.shutdownNow();
TimeUnit.SECONDS.sleep(1);
scheduler.shutdownNow();
}
public class RoomTask implements Runnable {
private final int id;
private Person currentPerson;
public RoomTask(int id) {
this.id = id;
}
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
currentPerson = persons.poll();
if (currentPerson == null) {
return;
}
monitor.put(id, currentPerson);
while (currentPerson.getRemainingTime() > 0) {
currentPerson.setRemainingTime(currentPerson.getRemainingTime() - 1);
TimeUnit.SECONDS.sleep(1);
}
monitor.remove(id);
} catch (InterruptedException e) {
return;
}
}
}
public Person getCurrentPerson() {
return currentPerson;
}
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
RestRoomSchedule s = new RestRoomSchedule(5);
s.start(10);
}
}
먼저 이번 로직의 핵심인 BlockingQueue에 대해서 설명해보려고 한다.
BlockingQueue는 Queue에서 아이템을 가져올 때 비어있으면 null을 리턴하지 않고 아이템이 추가될 때까지 기다린다.
반대로, 아이템을 추가할 때 Queue가 가득차 있으면 공간이 생길 때까지 기다린다.
ArrayBlockingQueue는 멀티 쓰레드 환경에서 사용하기 위해 구현되었으며 내부적으로 동시성에 안전하기 때문에
synchronized 구문 없이 사용 할 수 있다.
그리고 전 포스팅에 설명했던 멀티스레드를 생성하는 ExecutorService , 비동기적 연산의 결과를 표현하는 Future 이다.
그리고 화장실의 상황을 모니터해줄 ConcurrentMap, 스케쥴러를 사용한다.
ConcurrentMap은 멀티스레드에서 환경해서 사용 할 수 있도록 구현된 Map 클래스이다.
내부를 보면 get() 메소드에는 아예 synchronized가 존재하지 않고,
put() 메소드에는 중간에 synchronized 키워드가 존재하는 것을 볼 수 있다.
ConcurrentHashMap은 읽기 작업에는 여러 쓰레드가 동시에 읽을 수 있지만,
쓰기 작업에는 특정 세그먼트 or 버킷에 대한 Lock을 사용하도록 구현되어 있다.
그 다음 start 메서드에선 전달받은 사람 수를 기준으로 반복해서 이용객의 id와 랜덤한 10초내의 시간을 부여하고 블록 큐에 넣는다.
그 다음 스케쥴러 안에 Runnable 을 호출해서 run메서드를 구현해 모니터 스레드를 만들어 준다.
1초마다 반복되도록 설정했다.
그 다음 뒤에서 설명할 RoomTask 메서드를 화장실 수 만큼 스레드를 생성해서 submit 해준다.
get을 이용해 계산이 완료될 때 까지 기다렸다가 결과를 받는다.
RoomTask에선 블록큐의 값을 poll을 이용해 currentPerson에 담아준다.
자동적으로 동기화가 되어있기 때문에 따로 syncronized와 같은 키워드를 선언해줄 필요가 없다. ( 내부에 선언되어 있음 )
그 후 ConcurrentHashMap의 monitor에 값을 넣어주고 남은 시간이 0보다 작을 때 까지 1초 간격으로 스레드를 정지시키며 -1을 반복한다.
완료 후에는 모니터에서 해당 id값을 가진 이용객을 제거한다.
이제 main메서드에서 생성시킬 화장실 수 만큼 ResRoomSchedule 생성자에 넣어주고 원하는 사람 수를 start 메서드에 넣어준다.
'JAVA > JAVA 병렬프로그래밍' 카테고리의 다른 글
JAVA 병렬 프로그래밍 [4] - Sharing Objects ( 객체 공유 ) (0) | 2022.06.05 |
---|---|
JAVA 병렬 프로그래밍 [3] - 멀티 스레드로 1-100까지의 합 구하기 (0) | 2022.06.05 |
JAVA 병렬 프로그래밍 [2] - Thread Safety (0) | 2022.06.05 |
JAVA 병렬 프로그래밍 [1] (0) | 2022.04.16 |