java.util.concurrent.RejectedExecutionException 을 해결 하는 방법에 대해 예제 코드와 해결 방법에 대하여 이야기 해보겠습니다.
threads 를 만들어서 Executor 인터페이스를 이용하여 실행 하는 경우 실행 할 수 없는 상태가 될 수도 있습니다.
이것은 몇가지 이유가 있을 수 있고 아래의 예제를 통해 보여드리도록 하겠습니다.
그리고 RejectedExecutionException 은 java.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; | |
} | |
} |
아래의 코드는 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 이 아니라 적절한 값을 주면 됩니다.
소스
- Github.com
예제 출처 :
'공부 > 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 |