그럼, 배열의 크기를 계속해서 어떻게 확인 ?? ==> 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() 제공되는 메서드를 활용
이제, 이 두개의 메서드를 통해서 어떻게 문제를 해결하는지 알아보자.