스레드 대기 집합을 만들어 내는 객체라 할 수 있다. 객체처럼 private final Condition 컨디션 이름 = ReentrantLock 락 이름.newCondition()으로 만들어 내며, 이렇게 해서 여러 개의 스레드 대기 집합 만들어 낼 수 있다.
이렇게 만들어진 Condition에다가 여러 스레드를 원하는 대로 구별해서 넣어두면 wait(), notify()의 한계점이었던 '원하는 스레드를 깨울 수 없다'라는 점을 극복할 수 있다.
생산자-소비자 문제로 따지면 생산자 스레드만 따로 모아운 Condition을 깨우거나 소비자 스레드만 따로 모아운 Condition을 깨우거나 그럴 수 있게 되는 것이다. 이렇게 하면 원하는 스레드만 깨울 수 있어 좀 더 효율적인 작업이 가능해진다.
대기시키고자하는 컨디션 이름.await()로 사용되며 용도는 wait()랑 비슷하다. 인스턴스에 있는 스레드 대기 집합에 스레드를 보내는 게 아니라 컨디션의 스레드 대기 공간으로 스레드를 보내는 것뿐.
꺠우고자 하는 컨디션 이름.signal()롤 사용되면 용도는 notify()와 똑같다. 이것도 컨디션의 스레드 대기 공간을 호출한다는 점만 다르다.
import java.util.LinkedList;
import java.util.Queue;
import java.util.Random;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
// 공유 버퍼 클래스
class BufferWithCondition {
private Queue<Integer> queue;
private int capacity; // 버퍼의 최대 용량
// Lock 객체 생성 (ReentrantLock 사용)
private final Lock lock = new ReentrantLock();
// Condition 객체 생성: 버퍼가 가득 찼을 때 생산자 스레드를 대기시킬 조건
private final Condition notFull = lock.newCondition();
// Condition 객체 생성: 버퍼가 비었을 때 소비자 스레드를 대기시킬 조건
private final Condition notEmpty = lock.newCondition();
public BufferWithCondition(int capacity) {
this.queue = new LinkedList<>();
this.capacity = capacity;
}
// 생산자가 데이터를 버퍼에 추가하는 메서드
public void put(int value) throws InterruptedException {
// 락 획득
lock.lock();
try {
// 버퍼가 가득 찼으면 생산자는 notFull 조건에서 대기
while (queue.size() == capacity) {
System.out.println("버퍼 가득 참: 생산자 대기 중...");
notFull.await(); // notFull 조건에서 대기하며 락을 반납
}
// 버퍼에 데이터 추가
queue.offer(value);
System.out.println("생산: " + value + " (현재 크기: " + queue.size() + ")");
// 데이터를 추가했으니, notEmpty 조건에서 대기 중인 소비자에게 알림
notEmpty.signalAll(); // notEmpty 조건에서 대기 중인 모든 스레드를 깨움
} finally {
// 락 반납 (중요! 예외 발생 시에도 락이 풀리도록 finally 블록 사용)
lock.unlock();
}
}
// 소비자가 데이터를 버퍼에서 가져가는 메서드
public int get() throws InterruptedException {
int value;
// 락 획득
lock.lock();
try {
// 버퍼가 비어있으면 소비자는 notEmpty 조건에서 대기
while (queue.isEmpty()) {
System.out.println("버퍼 비어있음: 소비자 대기 중...");
notEmpty.await(); // notEmpty 조건에서 대기하며 락을 반납
}
// 버퍼에서 데이터 가져오기
value = queue.poll();
System.out.println("소비: " + value + " (현재 크기: " + queue.size() + ")");
// 데이터를 가져갔으니, notFull 조건에서 대기 중인 생산자에게 알림
notFull.signalAll(); // notFull 조건에서 대기 중인 모든 스레드를 깨움
} finally {
// 락 반납 (중요!)
lock.unlock();
}
return value;
}
}
// 생산자 스레드 클래스 (BufferWithCondition 사용)
class ProducerWithCondition extends Thread {
private BufferWithCondition buffer;
private Random random = new Random();
public ProducerWithCondition(BufferWithCondition buffer) {
this.buffer = buffer;
}
@Override
public void run() {
try {
for (int i = 0; i < 10; i++) { // 10개의 데이터를 생산
int value = random.nextInt(100);
buffer.put(value);
Thread.sleep(random.nextInt(500));
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.out.println("생산자 스레드 인터럽트됨.");
}
}
}
// 소비자 스레드 클래스 (BufferWithCondition 사용)
class ConsumerWithCondition extends Thread {
private BufferWithCondition buffer;
private Random random = new Random();
public ConsumerWithCondition(BufferWithCondition buffer) {
this.buffer = buffer;
}
@Override
public void run() {
try {
for (int i = 0; i < 10; i++) { // 10개의 데이터를 소비
buffer.get();
Thread.sleep(random.nextInt(500));
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.out.println("소비자 스레드 인터럽트됨.");
}
}
}
public class ConditionExample {
public static void main(String[] args) {
BufferWithCondition buffer = new BufferWithCondition(5); // 용량이 5인 버퍼 생성
ProducerWithCondition producer = new ProducerWithCondition(buffer);
ConsumerWithCondition consumer = new ConsumerWithCondition(buffer);
producer.start();
consumer.start();
try {
producer.join();
consumer.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.out.println("메인 스레드 인터럽트됨.");
}
System.out.println("\n--- Condition을 이용한 생산자-소비자 작업 완료 ---");
}
}