Thread의 선후 작업과 CountDownLatch
일련의 작업을 Thread로 구성했을 때 선/후 관계가 있는 경우가 왕왕 있다. 이때 하위 Thread의 작업이 완료될 때까지 main Thread가 기다려야 하는데 이때 join()이 사용된다. 아래의 경우를 살펴보자.
void useJoin() throws InterruptedException {
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < 5; i++) {
Thread t = new SubThread();
threads.add(t);
t.start();
}
// t가 끝날 때까지 대기할 것!!
for(Thread t: threads) {
t.join();
}
System.out.println("완전 종료됨");
}
class SubThread extends Thread {
@Override
public void run() {
try {
Thread.sleep(new Random().nextInt(10) * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "종료");
}
}
필요하다면 join 메서드 호출 시 대기할 시간을 주기도 한다.
CountDownLatch 활용
위의 join 동작을 간단히 처리할 수 있는 클래스 CountDownLatch라는 녀석이 JDK 1.5 버전부터 있는데 한번 사용해보자.
Latch는 걸쇠라는 뜻이다. CountDonw이 완료될 때 까지 잡아놓겠다는 뜻일까나??
CountDownLatch는 몇 번 카운트 다운 할것인지를 인자로 생성할 수 있다. 스레드들이 종료되면서 latch의 카운트 다운을 1씩 줄이다가 0이 되는 순간 latch가 종료된다.
CountDownLatch latch = new CountDownLatch(5);
CountDownLatch가 사용된 예를 살펴보자. SubThread2는 종료되면서 latch의 countDown()을 호출하는데 이때마다 latch를 생성하면서 주엇던 count가 1씩 감소한다.
class SubThread2 extends Thread {
@Override
public void run() {
try {
Thread.sleep(new Random().nextInt(10) * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// latch의 카운트를 1 줄인다.
latch.countDown();
System.out.println(Thread.currentThread().getName() + "종료");
}
}
main thread에서는 countdown이 0이 될 때 다음 동작을 하거나 특정 시간을 기다린 후 다음 동작을 수행할 수 있다.
void join2() throws InterruptedException {
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < 5; i++) {
Thread t = new SubThread2();
threads.add(t);
t.start();
}
// count가 0이 될때 까지 대기
latch.await();
// 설사 0이 되지 않더라도 3초간 대기 후 다음 동작으로 이동
//latch.await(1000*3, TimeUnit.MILLISECONDS);
System.out.println("완전 종료됨 2");
}
즉 await()는 count가 0이 될 때까지 기다리는 메서드이다. await(long timeout, TimeUnit unit)은 설사 count가 0이 되지 않더라도 주어진 timeout 시간이 종료되면 다음 동작으로 이동한다.