본문 바로가기

IT, 개발/JAVA

JAVA Thread - CountDownLatch 사용하여 작업 순서 설정하기

728x90
반응형

java 개발을 하다 보면 Thread를 사용하게 되는 경우가 종종 있는데, 오늘 알아볼 CountDownLatch는 멀티쓰레드 사용 시 병렬 처리방식의 단점을 보완해 줄 수 있습니다.

1. CountDownLatch를 사용하는 이유

만약, 3개의 쓰레드를 생성하고 각각의 작업을 병렬 처리방식으로 세팅했다면, 이 상황에서 각 쓰레드는 다른 쓰레드의 작업이 끝나는 것을 기다리지 않고 각자의 작업을 진행합니다.
하지만 전체적인 작업 흐름을 위해 3개의 쓰레드의 작업 순서를 통제해야 할 경우에 CountDownLatch의 await()를 활용하여 작업의 순서를 조정할 수 있습니다.

 

2. CountDownLatch 사용법

2-1. CountDwonLatch 생성

CountDownLatch threadCount = new CountDownLatch(3);

  ** 인자의 숫자는 count 숫자를 의미합니다.

      1 이상의 숫자를 사용자가 지정하여 생성할 수 있습니다.

2-2. countDown() 호출

threadCount.countDown();

 

  ** CountDownLatch의 count 숫자가 1씩 감소합니다.

2-3 await() 호출

threadCount.await();

  ** CountDownLatch의 count 숫자가 0이 될 때까지 대기합니다.

 

예를 들어 count 숫자를 3으로 생성한 상태에서 다른 쪽 쓰레드가 threadCount.countDown()을 3번 호출하면 1씩 감소되면서 count 숫자는 0이 되고, 대기하고 있던 threadCount.await()는 대기상태를 종료하고 이후 작업을 실행하게 됩니다.

 

3. CountDownLatch를 활용한 예제

3-1. await() 

예제를 통해 조금 더 자세히 살펴보겠습니다.

public class ThreadTest {
	private CountDownLatch countDownLatch = new CountDownLatch(1);
	private Thread1 thread1;
	private Thread2 thread2;

	// 쓰레드 시작
	public void threadStart() {
		thread1 = new Thread1();
		thread1.start();
		thread2 = new Thread2();
		thread2.start();
	}

	private class Thread1 extends Thread {
		public void run() {
		System.out.println("Thread1 start");
			try {
				// 5초 후 countDown() 호출
				Thread.sleep(5000);
				countDownLatch.countDown();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	private class Thread2 extends Thread {
		public void run() {
		System.out.println("Thread2 start");
			try {
				countDownLatch.await();			
				System.out.println("await end..");
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
}

먼저 Thread1, Thread2 두 개의 쓰레드를 실행시켰습니다. 
Thread2는 await()를 호출했기 때문에 대기상태로 들어가게 되고, Thread1에서 countDown()을 호출하면 Thread2의 await()는 대기상태를 끝내고 다음 작업을 진행하게 됩니다.

콘솔에서 결과를 확인해보면

Thread1 start
Thread2 start
await end..

쓰레드 시작 후 "Thread1 start", "Thread2 start"가 출력되고 5초 후 "await end.."가 출력되었습니다(작업 순서를 파악하기 위해 Thread.sleep(5000)을 사용하여 5초 딜레이).


이를 활용하게 된다면 우선순위가 높은 작업을 먼저 실행하도록 설정할 수 있고, 선행이 필요한 작업은 대기상태로 설정해 두었다가 선행작업이 끝난 다음 대기상태를 끝내고 다음 작업을 시작할 수 있게 통제할 수 있습니다.

3-1. await() time 설정 

그런데 만약 선행작업 중 오류가 생기거나 개발자가 원하는 시간보다 작업 처리 시간이 길어질 경우 countDown()을 호출하지 못하게 되어 await()는 무한 대기상태가 될 것이고, 그로 인해 다음 작업이 진행되지 않기 때문에 전체적인 결과에 큰 영향을 줄 수 있습니다.

 

이를 해결하는 방법은 await() 호출 시 시간을 설정하여 호출하는 것입니다. 그렇게 되면 countDown()이 호출되지 않더라도 정해진 시간이 지나면 대기상태를 끝내고 다음 작업을 시작할 수 있습니다. 

 

위의 예제에서 await()의 설정만 변경하여 실행해보겠습니다.

public class ThreadTest {
	private CountDownLatch countDownLatch = new CountDownLatch(1);
	private Thread1 thread1;
	private Thread2 thread2;

	// 쓰레드 시작
	public void threadStart() {
		thread1 = new Thread1();
		thread1.start();
		thread2 = new Thread2();
		thread2.start();
	}

	private class Thread1 extends Thread {
		public void run() {
		System.out.println("Thread1 start");
			try {
				// 5초 후 countDown() 호출
				Thread.sleep(5000);
				countDownLatch.countDown();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	private class Thread2 extends Thread {
		public void run() {
		System.out.println("Thread2 start");
			try {
				countDownLatch.await(2, TimeUnit.SECONDS);			
				System.out.println("await(2, TimeUnit.SECONDS) end..");
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
}

 

콘솔에서 결과를 확인해보면

Thread1 start
Thread2 start
await(2, TimeUnit.SECONDS) end..

countDown() 호출은 5초 후로 설정했지만 await() 설정 시 2초의 시간을 설정하였기 때문에 대기상태는 2초 후에 종료되었습니다.

 

예제를 보는것 만으로 확실하게 이해가 안 되신다면 예제를 테스트해보면서 직접 설정하다 보면 더 빠르게 이해될 수 있을 것 같습니다. 

CountDownLatch를 활용해서 더 효율적인 코드를 만들 수 있겠네요 ^^

728x90
반응형