고전적인 병렬 스레드 문제를 하나 가정하자. 아래의 코드는 하나의 List<String>을 여러 스레드가 동시에 접근하는 예이다.
import java.util.ArrayList;
import java.util.List;
public class MultiThread {
public static void main(String[] args) {
new MultiThread().run();
}
// 특정 List<String> 주어진 단어를 10000개 집어 넣는 스레드
private class MyThread extends Thread {
private final List<String> queue;
private final String word;
public MyThread(List<String> queue, String word) {
this.queue = queue;
this.word = word;
}
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
queue.add(word);
}
}
}
// 메인 로직
private void run() {
ArrayList<String> queue = new ArrayList<String>();
MyThread m1 = new MyThread(queue, "테스트1");
MyThread m2 = new MyThread(queue, "테스트2");
MyThread m3 = new MyThread(queue, "테스트3");
m1.start();
m2.start();
m3.start();
}
}
위와 같은 코드를 실행 해보면(전체 선택한 뒤 아무 패키지에나 붙여 넣으면 실행해 볼 수 있다), 바로 다음과 같은 에러를 볼 수 있다.
Exception in thread "Thread-2" java.lang.ArrayIndexOutOfBoundsException: 11621
at java.util.ArrayList.add(ArrayList.java:352)
at snippet.MultiThread$MyThread.run(MultiThread.java:23)
Exception in thread "Thread-0" java.lang.ArrayIndexOutOfBoundsException: 2294
at java.util.ArrayList.add(ArrayList.java:352)
at snippet.MultiThread$MyThread.run(MultiThread.java:23)
ERROR: JDWP Unable to get JNI 1.2 environment, jvm->GetEnv() return code = -2
JDWP exit error AGENT_ERROR_NO_JNI_ENV(183): [../../../src/share/back/util.c:820]
운이 좋으면 아예 에러가 나지 않거나, 완전히 다른 종류의 에러가 날 수도있다. 병렬 프로그래밍의 신명나는 백미는 이렇게 재현 불가능한 케이스로 개발자를 미치게 한다는 점이다.
이런 에러가 나는 이유는 하나의 List<String>을 여러 스레드가 동시에 접근하기 때문에, 접근 시점에 따라 문제가 발생하는 것이다.
Java의 synchronized 키워드 사용하기
import java.util.ArrayList;
import java.util.List;
public class MultiThread {
public static void main(String[] args) {
new MultiThread().run();
}
private class MyThread extends Thread {
private final List<String> queue;
private final String word;
public MyThread(List<String> queue, String word) {
this.queue = queue;
this.word = word;
}
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
synchronized (queue) {
queue.add(word);
}
}
}
}
private void run() {
ArrayList<String> queue = new ArrayList<String>();
MyThread m1 = new MyThread(queue, "테스트1");
MyThread m2 = new MyThread(queue, "테스트2");
MyThread m3 = new MyThread(queue, "테스트3");
m1.start();
m2.start();
m3.start();
}
}
자바의 synchronized 키워드를 이용하면 이 문제를 꽤 손쉽게 해결 할 수 있다. 이렇게 하면, 각 스레드들은 22번 줄을 실행하기전 queue의 모니터를 먼저 획득하고, 수행후 모니터를 놓아준다. 모니터가 이미 다른 스레드가 가지고 있는 경우라면, 모니터를 획득할 수 있을 때 까지 대기하게 된다.
그런데 특정 스레드가 오랬동안 queue를 점유하며 놓아주지 않는 경우, 어떻게 대처를 해야 할까? 또 배타적으로 접근해야 될 자원이 하나가 아니라 여러개인 경우, 이에 대한 추상화를 어떻게 해야 할까?
Eclipse Job Manager
JFace의 JobManager는 이러한 록 매커니즘을 제공한다. 이를 이용하여 위의 예제를 다시 작성하면...
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.runtime.jobs.ILock;
import org.eclipse.core.runtime.jobs.Job;
public class MultiThread {
public static void main(String[] args) {
new MultiThread().run();
}
private class MyThread extends Thread {
private final List<String> queue;
private final String word;
private final ILock lock;
public MyThread(ILock lock, List<String> queue, String word) {
this.lock = lock;
this.queue = queue;
this.word = word;
}
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
try {
lock.acquire(100);
queue.add(word);
} catch (InterruptedException e) {
System.out.println("0.1 초간 대기했지만, queue를 확보하지 못했습니다.");
} finally {
lock.release();
}
}
}
}
private void run() {
ArrayList<String> queue = new ArrayList<String>();
ILock lock = Job.getJobManager().newLock();
MyThread m1 = new MyThread(lock, queue, "테스트1");
MyThread m2 = new MyThread(lock, queue, "테스트2");
MyThread m3 = new MyThread(lock, queue, "테스트3");
m1.start();
m2.start();
m3.start();
}
}
27 에서 록을 소유하기 위한 제한 시간을 지정할 수 있고 29실패시의 처리를 지정할 수 있다.32에서 록을 명시적으로 릴리즈하는 것도 눈여겨 보자.
이렇게하면, 교착 현상시 발생할 수 있는 기아도 제거할 수 있다. ILock의 기본 구현과 록 매커니즘이 순환 록 상황을 자동으로 검사하여, 해당 스레드에 익셉션을 발생시켜 탈출 시켜주기 때문이다. 따라서 작업이 정상적으로 수행안될지는 몰라도, 사용자가 영원히 끝나지 않는 작업을 기다릴 일은 발생하지 않게 된다.
'Java' 카테고리의 다른 글
아규먼트(argument) 와 파라미터(parameter) 의 차이 (0) | 2013.10.17 |
---|---|
Java에서 Linux Shell 명령어 실행하기 (0) | 2013.08.26 |
Java에서 SSH를 통해 리눅스 서버에 명령을 전달하는 코드 (1) | 2013.08.26 |
String.split 과 StringTokenizer의 차이 (0) | 2013.08.12 |
[Struts2] 취약점 관련 Struts 2.3.15.1로 라이브러리 변경 (0) | 2013.07.24 |