Java adv1 - 생산자,소비자 문제 2

dev1·2024년 12월 2일

그럼, 배열의 크기를 계속해서 어떻게 확인 ?? ==> while

[[[ 배열 ( 저장공간 ) 을 버퍼라고 하자. ]]]

package thread.bounded;

import java.util.ArrayDeque;
import java.util.Queue;

import static util.MyLogger.log;
import static util.ThreadUtils.sleep;

public class BoundedQueueV2 implements BoundedQueue {

    private final Queue<String> queue = new ArrayDeque<>();
    private final int max;


    public BoundedQueueV2(int max) {
        this.max = max;
    }

    @Override
    public synchronized void put(String data) {

        while ( queue.size() == max ){
            log("[put] 큐가 가득참, 생산자 대기");
            sleep(1000);
        }
        queue.offer(data);
    }

    @Override
    public synchronized String take() {
        while (queue.isEmpty()) {
            log("[take] 큐 비어있음, 소비자 대기");
            sleep(1000);
        }
        return queue.poll();
    }

    @Override
    public String toString() {
        return "queue=" + queue +
                '}';
    }
}

이후에 이 코드를 통해서 객체를 생성 ...

package thread.bounded;

import java.util.ArrayList;
import java.util.List;

import static util.MyLogger.log;
import static util.ThreadUtils.sleep;

public class boundedMain2 {

    public static void main(String[] args) {
        // queue 선택
        BoundedQueue queue = new BoundedQueueV2(2);

        // 생산자, 소비자 실행 순서 선택 /// 반드시 한개만
        producerFirst(queue);
        //consumerFirst(queue);
    }

    private static void consumerFirst(BoundedQueue queue) {
        log("== [소비자 먼저 실행] 시작, " + queue.getClass().getSimpleName() + "==");
        List<Thread> threads = new ArrayList<>();
        startConsumer(queue, threads);
        printAllState(queue, threads);
        startProducer(queue, threads);
        printAllState(queue, threads);
        log("== [소비자 먼저 실행] 종료, " + queue.getClass().getSimpleName() + "==");
    }

    private static void producerFirst(BoundedQueue queue) {
        log("== [생산자 먼저 실행] 시작, " + queue.getClass().getSimpleName() + "==");
        List<Thread> threads = new ArrayList<>();
        startProducer(queue, threads);
        printAllState(queue, threads);
        startConsumer(queue, threads);
        printAllState(queue, threads);
        log("== [생산자 먼저 실행] 종료, " + queue.getClass().getSimpleName() + "==");

    }

    private static void startConsumer(BoundedQueue queue, List<Thread> threads) {
        System.out.println();
        log("소비자 시작");
        for (int i = 1; i <= 3; i++) {
            Thread consumer = new Thread(new ConsumerTask(queue), "consumer" + i);
            threads.add(consumer);
            consumer.start();
            sleep(100);
        }
    }

    private static void printAllState(BoundedQueue queue, List<Thread> threads) {
        System.out.println();
        log("현재 상태 출려그 큐 데이터 : " + queue);
        for (Thread thread : threads) {
            log(thread.getName() + " 상태 : " + thread.getState());
        }
    }

    private static void startProducer(BoundedQueue queue, List<Thread> threads) {
        System.out.println();
        log("생산자 시작");
        for (int i = 1; i <= 3; i++) {
            Thread producer = new Thread(new ProducerTask(queue, "data" + i), "producer" + i );
            threads.add(producer);
            producer.start();
            sleep(100);
        }
    }


}

====> 이렇게 코드를 작성하고 시작

14:42:32.560 [     main] == [생산자 먼저 실행] 시작, BoundedQueueV2==

14:42:32.561 [     main] 생산자 시작
14:42:32.568 [producer1] [생산 시도] data1 -> queue=[]}
14:42:32.568 [producer1] [생산 완료] data1 -> queue=[data1]}
14:42:32.669 [producer2] [생산 시도] data2 -> queue=[data1]}
14:42:32.670 [producer2] [생산 완료] data2 -> queue=[data1, data2]}
14:42:32.775 [producer3] [생산 시도] data3 -> queue=[data1, data2]}
14:42:32.775 [producer3] [put] 큐가 가득참, 생산자 대기

14:42:32.878 [     main] 현재 상태 출려그 큐 데이터 : queue=[data1, data2]}
14:42:32.878 [     main] producer1 상태 : TERMINATED
14:42:32.878 [     main] producer2 상태 : TERMINATED
14:42:32.878 [     main] producer3 상태 : TIMED_WAITING

14:42:32.878 [     main] 소비자 시작
14:42:32.879 [consumer1] [소비 시도]     ? <- queue=[data1, data2]}
14:42:32.983 [consumer2] [소비 시도]     ? <- queue=[data1, data2]}
14:42:33.088 [consumer3] [소비 시도]     ? <- queue=[data1, data2]}

14:42:33.189 [     main] 현재 상태 출려그 큐 데이터 : queue=[data1, data2]}
14:42:33.189 [     main] producer1 상태 : TERMINATED
14:42:33.190 [     main] producer2 상태 : TERMINATED
14:42:33.190 [     main] producer3 상태 : TIMED_WAITING
14:42:33.190 [     main] consumer1 상태 : BLOCKED
14:42:33.190 [     main] consumer2 상태 : BLOCKED
14:42:33.190 [     main] consumer3 상태 : BLOCKED
14:42:33.190 [     main] == [생산자 먼저 실행] 종료, BoundedQueueV2==
14:42:33.780 [producer3] [put] 큐가 가득참, 생산자 대기
14:42:34.783 [producer3] [put] 큐가 가득참, 생산자 대기
14:42:35.789 [producer3] [put] 큐가 가득참, 생산자 대기
14:42:36.792 [producer3] [put] 큐가 가득참, 생산자 대기
14:42:37.798 [producer3] [put] 큐가 가득참, 생산자 대기
14:42:38.800 [producer3] [put] 큐가 가득참, 생산자 대기
14:42:39.806 [producer3] [put] 큐가 가득참, 생산자 대기
14:42:40.811 [producer3] [put] 큐가 가득참, 생산자 대기
14:42:41.814 [producer3] [put] 큐가 가득참, 생산자 대기
14:42:42.819 [producer3] [put] 큐가 가득참, 생산자 대기
14:42:43.825 [producer3] [put] 큐가 가득참, 생산자 대기
14:42:44.829 [producer3] [put] 큐가 가득참, 생산자 대기
14:42:45.832 [producer3] [put] 큐가 가득참, 생산자 대기
14:42:46.837 [producer3] [put] 큐가 가득참, 생산자 대기
14:42:47.843 [producer3] [put] 큐가 가득참, 생산자 대기
14:42:48.848 [producer3] [put] 큐가 가득참, 생산자 대기
14:42:49.852 [producer3] [put] 큐가 가득참, 생산자 대기
14:42:50.857 [producer3] [put] 큐가 가득참, 생산자 대기
14:42:51.862 [producer3] [put] 큐가 가득참, 생산자 대기
14:42:52.868 [producer3] [put] 큐가 가득참, 생산자 대기

이렇게 이상하게 작동 ...

왜 이렇게 작동하는걸까?

===> synchronized ?

producer3 상태 : TIMED_WAITING 인 상태

그렇다면, 다른 스레드는 접근이 불가능

===> consumer1, 2 , 3 ===> 접근이 불가능 하니까 소비를 완료하지못함

//// 소비완료를 못하고있는상황 ( 기존메인에서는 소비완료됨 ).

그니까, producer3 : TIMED_WAITING /// 왜? synchronized 때문에...

즉, while 문으로 버퍼공간이 있을때까지 계속해서 producer3 가 확인

====> 해당 객체에대한 LOCK ==> producer3 가 가지고있는 상태 ...

즉, 이러한상황이면 다른 스레드는 접근이 불가능 ( 버퍼에 접근불가 ).

그렇기때문에, 소비를 할수도없는상황 ( 위 실행결과를 확인하면 마지막에 producer3 가 계속 대기중 .. )

이러한 방식이면, 절대로 데이터를 이상황에서 넣을수도 / 소비할수도 없는상태로 남게된다.

왜냐하면, p3 이 완료되고나서 락을 반납하고, c1 / 2 / 3 가 실행될거니까...

그럼 이를 어떻게 해결해야할까?

즉, p3 는 계속해서 자리가 빌때까지 대기하고, c1/c2/c3 는 소비하고....

이러한 형식으로 코드를 어떻게 짤 수 있을까?

==> 스레드가 락을 가지고 대기할때가 문제 ....

sleep(1000) 을 통해서, 1초 동안 아무것도 하지 않는 상태

===> 이 코드만으로는 문제가 되지않지만, 락을 가지고 이 코드를 수행하기때문에 문제

그럼, 대기하는동안에 // 락을 반납하고 다른 스레드가 이 락을 통해서 객체에 접근할 수 있으면?

==> 해결완료

그럼 어떻게 락을 다른스레드에 잠깐 넘겨줄수있을까

===> Object.wait() / Object.notify() 제공되는 메서드를 활용

이제, 이 두개의 메서드를 통해서 어떻게 문제를 해결하는지 알아보자.

0개의 댓글