반응형

 

자바 - 스레드 동기화 wait()와 notify(), notifyAll() 사용하기

 

 

멀티스레드 환경에서 동기화를 하더라도 상황에 따라 여러가지 문제점이 있을 수 있습니다. 그중 하나가 제한된 자원에 접근하는 producer-consumer 문제입니다. 여기서는 이러한 멀티스레드 환경에서 효율적인 동기화를 위해 제공되는 wait()와 notify(), notifyAll() 메소드에 대해서 알아보도록 하겠습니다. 

 

 

   목차

  1. 스레드 동기화 문제
  2. wait()와 notify(), notifyAll() 메소드
  3. 사용예제

 

 

1. 스레드 동기화 문제

 

멀티스레드 환경에서는 synchronized 키워드를 사용하여 동기화를 하더라도 고려해야 할 것들이 많이 있습니다. 그중 하나가 producer-consumer 문제입니다.

예를들어 3개의 상품(product)만 놓아둘 곳이 있는 마켓(market)에 물건을 공급해주는 생산자(producer)와 물건을 소비하는 소비자(consumer)가 있을 경우, 생산자는 3개의 물건이 이미 마켓에 가득 찬 경우에 추가로 물건을 공급할 수 없기 때문에 문제가 생길 수 있고, 소비자는 마켓에 물건이 하나도 없을 경우에 물건을 소비할 수 없기 때문에 문제가 생길수 있습니다. 

이러한 경우에 적절한 동기화 처리를 하기 위해서 wait()와 notify(), notifyAll() 메서드가 사용될 수 있습니다. 

 

[해결방안]

 

1) 마켓에 물건이 가득 찬 경우

  1. 마켓에 물건이 가득 찼을 경우에는 생산자(producer)가 더이상 생산하지 않고 기다리게 한다. - wait()
  2. 소비자(consumer)가 물건을 소비한 후에 생산자(producer)에게 알려준다. - notify() or notifyAll()

 

2) 마트에 물건이 없을 경우

  1. 마켓에 물건이 없을 경우에는 소비자(consumer)가 더이상 소비하지 못하고 기다리게 한다. - wait()
  2. 생산자(producer)가 물건을 생산한 후에 소비자(consumer)에게 알려준다. - notify() or notifyAll()

 

 

 

2. wait()와 notify(), notifyAll() 메소드

 

이전에 Object 클래스에 대해 알아보았을 때, Object 클래스에 스레드와 관련하여 wait()와 notify(), notifyAll() 메서드가 있다는 것을 확인할 수 있었습니다. Object 클래스는 최상위 클래스로 모든 객체가 상속을 받는 클래스이기 때문에 모든 객체는 동기화 객체로 사용시 해당 메소드를 사용할 수 있습니다. 또한 스레드와 관련된 이 메소드들은 synchronized로 지정된 임계영역(critical section) 안에서만 사용이 가능합니다. 

 

  1. void wait() : 현재 스레드를 다른 스레드가 이 객체에 대한 notify() 또는 notifyAll() 메소드를 호출할때까지 대기합니다. 
  2. void wait(long timeout) : 현재 스레드를 다른 스레드가 이 객체에 대한 notify() 또는 notifyAll() 메소드를 호출하거나 timeout 시간동안 대기합니다. 
  3. void notify() : 이 객체에 대해 대기중인 스레드 하나를 깨웁니다. 
  4. void notifyAll() : 이 객체에 대해 대기중인 모든 스레드를 깨웁니다. 

 

 

 

3. 사용예제

 

1) 클래스 정의 

public class Market {
	private int count_product = 0;
	final private int MAX = 3; 
	final private int MIN = 0;
	
	synchronized public void add() {
		System.out.print("[추가전" + count_product + "]");
		while (count_product >= MAX) {
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		} 
		count_product++;
		notifyAll();
		System.out.print("[추가후" + count_product + "]");
	}
	
	synchronized public void remove() {
		System.out.print("[삭제전" + count_product + "]");
		while (count_product <= MIN) {
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		count_product--;
		notifyAll();
		System.out.print("[삭제후" + count_product + "]");
		
	}

	synchronized public int getCount_product() {
		return count_product;
	}
}

class ProducerThread extends Thread {
	Market m;
	
	public ProducerThread(Market m) {
		this.m = m;
	}

	@Override
	public void run() {
		for (int i=0; i<10; i++) {	
			HelloWorld.threadSleep((int)(Math.random()*10));
			m.add();
		}
	}
}

class ConsumerThread extends Thread {
	Market m;
	
	public ConsumerThread(Market m) {
		this.m = m;
	}

	@Override
	public void run() {
		for (int i=0; i<10; i++) {
			HelloWorld.threadSleep((int)(Math.random()*10));
			m.remove();
		}
	}
}

 

2)  객체 생성 및 실행

public class HelloWorld {
	public static void main(String[] args) {
		Market m = new Market();
		ProducerThread pt = new ProducerThread(m);
		ConsumerThread ct = new ConsumerThread(m);
		
		pt.start();
		ct.start();
		
		try {
			pt.join();
			ct.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		System.out.printf("%nResult: " + m.getCount_product());
	}
	
	static void threadSleep(long time) {
		try {
			Thread.sleep(time);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

 

3) 실행결과

[추가전0][추가후1][추가전1][추가후2][추가전2][추가후3][추가전3][삭제전3][삭제후2][추가후3]
[삭제전3][삭제후2][삭제전2][삭제후1][삭제전1][삭제후0][추가전0][추가후1][삭제전1][삭제후0]
[추가전0][추가후1][추가전1][추가후2][추가전2][추가후3][삭제전3][삭제후2][추가전2][추가후3]
[삭제전3][삭제후2][추가전2][추가후3][삭제전3][삭제후2][삭제전2][삭제후1][삭제전1][삭제후0]
Result: 0

 

반응형

+ Recent posts