간펴니
간편 자바프로그래밍
간펴니
전체 방문자
오늘
어제
  • 전체보기 (185)
    • 알고리즘 (2)
    • JAVA (69)
      • 이펙티브 자바 (47)
      • JAVA 병렬프로그래밍 (5)
      • 자바 (17)
    • SPRING (60)
      • Spring (12)
      • IceWater Community (37)
      • Homme Shop (10)
      • 토비의 스프링 (1)
    • SPRING BOOT (4)
      • WhiteRecord (7)
    • 오류 (9)
    • DB (10)
      • ORACLE (5)
      • MYSQL (1)
      • MYBATIS (4)
      • JPA (0)
      • 대용량 데이터 베이스 (0)
      • SQL (0)
    • FRONT (8)
      • JSP (2)
      • JavaScript (2)
      • Jquery (3)
      • Thymeleaf (1)
    • AWS (6)
    • JNI (10)
    • 회고 (0)
    • MQ (0)
    • Radis (0)
    • Git (0)
    • Docker (0)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

  • 블로그 컨셉 변경

인기 글

태그

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
간펴니

간편 자바프로그래밍

JAVA/JAVA 병렬프로그래밍

JAVA 병렬 프로그래밍 [5] - 화장실 스케쥴링

2022. 6. 6. 00:26
728x90

조건

 

화장실의 칸은 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 메서드에 넣어준다.

 

 

 

 

 

728x90
저작자표시

'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
    'JAVA/JAVA 병렬프로그래밍' 카테고리의 다른 글
    • JAVA 병렬 프로그래밍 [4] - Sharing Objects ( 객체 공유 )
    • JAVA 병렬 프로그래밍 [3] - 멀티 스레드로 1-100까지의 합 구하기
    • JAVA 병렬 프로그래밍 [2] - Thread Safety
    • JAVA 병렬 프로그래밍 [1]
    간펴니
    간펴니
    개발공부 기록하는 곳

    티스토리툴바