공부/java2014. 9. 3. 15:39

java.util.concurrent.RejectedExecutionException 을 해결 하는 방법에 대해 예제 코드와 해결 방법에 대하여 이야기 해보겠습니다.

threads 를 만들어서 Executor 인터페이스를 이용하여 실행 하는 경우 실행 할 수 없는 상태가 될 수도 있습니다.

이것은 몇가지 이유가 있을 수 있고 아래의 예제를 통해 보여드리도록 하겠습니다.

그리고 RejectedExecutionExceptionjava.lang.RuntimeException 이라는 것을 주목 할 필요가 있습니다.

1. 간단한 Executor 샘플

RejectedExecutionException 예외를 설명 하기 위해 ThreadPoolExecutor 를 이용하여 몇개의 worker thread 들을 실행하는 간단한 java 어플리케이션을 만들것입니다.

어떻게 만들었는지 아래의 코드를 보겠습니다.

package kr.co.leehana.example.exception.rejectedexecutionexception;
public class Worker implements Runnable {
private int id;
public Worker(int id) {
this.id = id;
}
@Override
public void run() {
try {
Thread currentThread = Thread.currentThread();
System.out.println(currentThread.getName()
+ " currently execution the task " + id);
Thread.sleep(500);
System.out.println(currentThread.getName()
+ " just completed the task " + id);
} catch (Exception e) {
System.err.println(e);
}
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
view raw Worker.java hosted with ❤ by GitHub

아래의 코드는 ThreadPoolExecutor 클래스가 어떻게 당신이 만든 task 들을 몇개의 thread 들을 만들어 실행하는지 보여줍니다.

package kr.co.leehana.example.exception.rejectedexecutionexception;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class RejectedExecutionExceptionExample1 {
public static void main(String[] args) throws InterruptedException {
ExecutorService executor = new ThreadPoolExecutor(3, 3, 0L,
TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(15));
normalRun(executor);
}
private static void normalRun(ExecutorService executor) {
Worker tasks[] = new Worker[10];
for (int i = 0; i < 10; i++) {
tasks[i] = new Worker(i);
executor.execute(tasks[i]);
}
executor.shutdown();
}
}

프로그램을 실행하면 정상적으로 실행 되는 것을 확인할 수 있습니다.

우리는 ThreadPoolExecutor 를 이용하였는데 pool 의 크기를 3으로 설정 하였습니다.

이것이 의미 하는것은 3개의 thread 들을 만들것이고 3개의 task worker 들을 채워 넣을것입니다.

우리가 새로운 task 를 만들어 ThreadPoolExecutor 에 submit 을 하게 되면 3개의 pool 중 하나가 사용 가능 하게 될때까지 BlockingQueue 에 배치하고 대기 하게 됩니다.

이 예제의 경우 15의 크기를 가지는 ArrayBlockingQueue 를 사용합니다. ArrayBlockingQueue 를 사용한 이유는 나중에 설명 합니다.

예제에서 우리는 10개의 task 들을 만들어 실행 할 것입니다.

2. 간단한 RejectedExecutionException 예제

RejectedExecutionException 의 한가지 이유중에는 새로운 task 를 executor 가 shutdown 된 후에 실행 하려고 할때 발생합니다.

shutdown 메소드가 호출 된 후에는 오래된 task 들은 여전히 실행되지만, 더이상 새로운 task 들을 실행 할 수 없게 됩니다.

이 오류에 대한 내용을 아래의 예제를 통해 확인 해보겠습니다.

package kr.co.leehana.example.exception.rejectedexecutionexception;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class RejectedExecutionExceptionExample2 {
public static void main(String[] args) throws InterruptedException {
ExecutorService executor = new ThreadPoolExecutor(3, 3, 0L,
TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(15));
exceptionRun(executor);
}
private static void exceptionRun(ExecutorService executor) {
Worker tasks[] = new Worker[10];
for (int i = 0; i < 10; i++) {
tasks[i] = new Worker(i);
executor.execute(tasks[i]);
}
executor.shutdown();
executor.execute(tasks[0]);
}
}

프로그램을 실행하게 되면 RejectedExecutionException 이 출력 될 것입니다.

3. RejectedExecutionException 두번째 예제

Executor 가 그의 책임 아래 더이상 task 들을 처리 할 수 없을때 나타나는 예제 입니다.

로컬 메모리의 한계에 도달했을 때 Exception 이 발생합니다.

이 예제에서 로컬 메모리는 ArrayBlockingQueue 의 크기인 15 에 해당합니다.

만약 ArrayBlockingQueue 에 저장 할 수 있는 것보다 더 많은 task 들을 실행 하려고 하면 RejectedExecutionException 이 발생합니다.

예제를 살펴 보겠습니다.

package kr.co.leehana.example.exception.rejectedexecutionexception;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class RejectedExecutionExceptionExample3 {
public static void main(String[] args) throws InterruptedException {
ExecutorService executor = new ThreadPoolExecutor(3, 3, 0L,
TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(15));
normalRun(executor);
}
private static void normalRun(ExecutorService executor) {
Worker tasks[] = new Worker[20];
for (int i = 0; i < 20; i++) {
tasks[i] = new Worker(i);
executor.execute(tasks[i]);
}
executor.shutdown();
}
}

15의 크기를 가지는 ArrayBlockingQueue 는 자신의 크기인 15 보다 더 많은 thread 들을 저장 할 수 없습니다.

하지만 예제에서 우리는 20개의 thread 들을 실행 하려고 하였습니다.

20개의 thread 가 빠르게 처리 된다면 오류가 발생 하지 않을 수도 있지만 우리는 Worker.java 코드에서 Thread.sleep() 으로 딜레이를 주었기 때문에 ThreadPoolExecutor 에서 빠르게 처리 하지 못하고 BlockingQueue 에 저장하게 되는데 이때 15개를 넘어가기 때문에 오류가 발생 한것입니다.

만약 18개의 thread 들을 만들었다면 정상적으로 실행 될 것입니다. 한번 생각해보는것도 좋을꺼 같습니다.

4. RejectedExecutionException 해결 방안

먼저 다음 두가지의 경우를  체크 해봅니다.

  1) shutdown 메소드가 호출 된 후 Executor 가 실행 되는것을 주의 해서 프로그래밍을 한다.

  2) Executor 가 처리 할 수 있는 것보다 더 많은 task 들을 실행 하지 않는다.

두번째의 경우는 쉽게 해결이 가능 합니다. 크기에 제한이 없는 자료구조를 이용 하면 됩니다.

예를 들면 LinkedBlockingQueue 가 있습니다.

만약 LinkedBlockingQueue 를 사용 하였는데도 여전히 문제가 발생한다면 첫번째 케이스를 의심 해봐야 합니다.

만약 첫번째 케이스에도 해당하지 않는다면 분명 여러가지 복합적인 문제가 있을 수 있습니다.

예를 들면 다른 thread 들이 deadlock 에 걸려 메모리가 꽉 차거나 LinkedBlockingQueue 는 제한 없이 계속 쌓이므로 메모리의 사용이 많아져서 문제가 발생 할 수 있습니다.

우리는 두번째 케이스를 좀 더 세밀하게 촛점을 맞춰 해결하려고 합니다.

ThreadPoolExecutor 는 15개 보다 더 많은 task 들을 처리 할 수 있습니다. 

이 예제에서는 ArrayBlockingQueue 를 사용하고 있고 이는 새로운 task 가 실행되기 전에 처리가 완료된다면 자연스럽게 오류가 없이 실행이 될 수 있습니다.

약간의 꼼수 같은 예제 이지만 한번 살펴 보겠습니다.

package kr.co.leehana.example.exception.rejectedexecutionexception;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class RejectedExecutionExceptionExample4 {
public static void main(String[] args) throws InterruptedException {
ExecutorService executor = new ThreadPoolExecutor(3, 3, 0L,
TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(15));
exceptionSolvedRun(executor);
}
private static void exceptionSolvedRun(ExecutorService executor)
throws InterruptedException {
Worker tasks[] = new Worker[20];
for (int i = 0; i < 10; i++) {
tasks[i] = new Worker(i);
executor.execute(tasks[i]);
}
Thread.sleep(3000);
for (int i = 10; i < 20; i++) {
tasks[i] = new Worker(i);
executor.execute(tasks[i]);
}
executor.shutdown();
}
}

예제를 실행 해보면 문제없이 실행 되는것을 확인 할 수 있습니다.

예제를 실행해 보면 마치 ThreadPoolExecutor 가 15개 이상의 task 들을 처리 할 수 있는 것 처럼 보이지만 사실 약간의 꼼수를 이용하여 딜레이를 줘 ThreadPoolExecutor 가 이미 처리 중인 task 들이 충분히 처리 되어 queue 를 비울 수 있는 시간을 벌게 해줬기 때문에 가능한것이다. Thread.sleep(3000); 이부분은 꼭 3000 이 아니라 적절한 값을 주면 됩니다.




소스

예제 출처 : 


'공부 > java' 카테고리의 다른 글

Java List 를 배열로 변환 하는 예제  (0) 2014.09.05
Java FileWriter 예제  (0) 2014.09.05
예외  (0) 2014.06.20
POJO  (0) 2014.06.10
자바의 주요 특징 및 개인적인 생각  (0) 2014.06.08
Posted by #HanaLee