스레드는 스레드 스케쥴링(우선순위 할당)이 이뤄지기전 스레드는 실행대기 상태로 변경되며 이상태에서 스레드 스케쥴링이 이뤄진후 선택된 스레드가 CPU를 점유 및 run메서드를 실행한다.
(일시정지 상태에서 실행으로 넘어가기위해서는 실행대기 상대를 무조건 거쳐야한다)
스레드의 상태는 다음과 같이 분류할수 있다.
//스레드의 흐름도는 하단의 사진 참조
//생성
NEW(생성완료) - 스레드 객체는 생성되었으나 start() 메서드가 미호출
//실행대기
RUNNABLE(실행 대기중) - 실행상태로 언제든 갈수있는상태
//일시대기
WAITING(후순위로 대기중) - 다른스레드가 통지할때까지 대기
TIMED_WAITING(실행중 대기상태) -일정시간동안 대기
BLOCKED(해제까지 대기) - 락이 푸릴떄까지 대기
//종료
TERMINATED(종료) - 실행끝
//사용예시
Thread.State.NEW -> 객체생성상태일때 실행 대기상태로 변경
Thread.State.TERMINATED -> 종료상태
Thread.getState(); -> 현재 스레드 상태얻기
스레드 상태제어
상단에선 스레드의 생성, 실행,종료 3단계를 확인하였으니 이번엔 스레드의 상태제어를 어떤식으로하는지 확인해보겠다. (스레드 상태제어 -> 실행중인 스레드의 상태를 변경하는것)
동영상 플레이어 실행 중 영상의 일시정지, 음소거, 자막삽입등 우리가 사용하는 프로그램에선 여러가지 기능(스레드)을 활용하는데 이것을 조정하는게 스레드 상태제어라고 이야기한다.
상태제어의 흐름도와 대표적으로 사용되는 메서드는 다음과 같다

상태제어 메서드
메소드 | 설명 |
interrupt() | 일시 정지 상태의 스레드에서 InterruptedException 예외를 발생시켜, 예외 처리 코드(catch)에서 실행 대기 상태로 가거나 종료 상태로 갈 수 있도록 한다. |
notify() norifyAll() |
동기화 블록 내에서 wait() 메소드에 의해 일시 정지 상태에 있는 스레드를 실행 대기 상태로 만든다. |
resume() | suspend() 메소드에 의해 일시 정지 상태에 있는 스레드를 실행 대기 상태로 만든다. - Deprecated(대신 notify(), notifyAll() 사용) |
sleep(long millis) sleep(long millis, int nanos) |
주어진 시간 동안 스레드를 일시 정지 상태로 만든다. 주어진 시간이 지나면 자동적으로 실행 대기 상태가 된다. |
join() join(long millis) join(long millis, int nanos) |
join() 메소드를 호출한 스레드는 일시 정지 상태가 된다. 실행 대기 상태로 가려면, join() 메소드를 멤버로 가지는 스레드가 종료되거나, 매개값으로 주어진 시간이 지나야 한다. |
wait() wait(long millis) wait(long millism, int nanos) |
동기화(Synchronized) 블록 내에서 스레드를 일시 정지 상태로 만든다. 매개값으로 주어진 시간이 지나면 자동적으로 실행 상태가 된다. 시간이 주어지지 않으면 notify(), notifyAll() 메소드에 의해 실행 대기 상태로 갈 수 있다. |
suspend() | 스레드를 일시 정지 상태로 만든다. resume() 메소드를 호출하면 다시 실행 대기 상태가 된다. - Deprecated(대신 wait() 사용) |
yield() | 실행중에 우선순위가 동일한 다른 스레드에게 실행을 양보하고 실행 대기 상태가 된다. |
stop() | 스레드를 즉시 종료시킨다. - Depecated (가능한 쓰지말것) |
sleep() - 일정시간동안 정지(시간 경과시 실행대기상태)
주어진 시간동안 일시정지, 시간 경과시 실행대기로 변환
Thread.sleep(1000); //sleep의 단위는 밀리세컨드(1/1000)이다.
yield() - 우선순위가 같거나 높은 스레드에게 실행양보(호출한 스레드가 실행대기상태)
실행한 스레드는 실행대기가 되며 자신보다 우선, 동일한 순위의 스레드에게 실행을 양보한다
반복적인 작업(for,while)을 하는경우 이것이 진행중임을 확인하기위해 일정한 코드를 넣어 확인하는 경우가 있는데 (우리가 핸드폰을 터치중일경우 핸드폰은 하루종일 터치받는 신호를 1밀리초마다 받고있는것이다) 이것이 언제 종료될것인지 정해주지 않는다면 계속 무의미한 반복(메모리 낭비)을 하게되는데 이런낭비를 다른곳으로 돌려쓰기위한 메서드가 yield()이다.
Thread.yield();
join() - 다른 스레드의 종료를 기다림
스레드의 우선순위를 정했지만 선행되야할 스레드를 무시한채 후순위 스레드부터 시작하는경우 예상과 다른 결과가 출력된다. 이러한 문제를 해결하기 위해 사용하는것이 join()메서드이다.
(게임에서 A스테이지가 끝나야 B스테이지로 넘어가는데 버그로 B스테이지부터 시작하는경우 몬스터나 오브젝트가 정상적으로 안되는경우가 있지않던가?)
Thread.start(); //스레드시작
Thread.join(); //일시정지
wait(), notify(), notifyAll() - 스레드간협업
wait(); - 일시정지
notify() - 일시정지에서 실행대기로 변경
notifyAll() - wait에 의해 일시정지된 모든 스레드를 전부 실행대기로 만든다
두개 이상의 스레드를 교대로 번갈아가며 실행할경우 사용하며 호출한 스레드는 실행대기상태로 된다.
(캐릭터 2~3개 또는 A,B모드를 한번에 조종한다고 생각하면 이해할것이다)
wait를 사용하기 위해선 공유객체는 두 스레드가 작업할 내용을 각각 동기화 메서드(Synchronized Method)로 구분한뒤 한 스레드가 작업을 완료한경우 notify()를 호출하여 일시정지 상태에 있는 다른 스레드를 실행대기 상태로 만들고 자신은 wait()를 호출하여 일시정지로 만든다.
wait()에 값을 넣는경우 notify()를 호출하지 않아도 값(시간)이 지나면 자동적으로 실행대기 상태가 된다.
//일시정지 = wait()호출, 일시정지 -> 실행대기로 변경 - notify()호출
//초기
A - 실행대기
B - 실행대기
//A실행
A - 실행중
B - 실행대기
//A 종료 및 B 실행
A - 일시정지 (wait())
B - 실행대기 -> 실행
//B 종료 A 실행
A - 일시정지 -> 실행대기 -> 실행 (notify())
B - 일시정지 (wait())
스레드 종료 - Stop플래그, interrupt()
run 메서드가 모두 실행되면 종료되는데 이것이 비정상적으로 종료되는경우 기존에 사용되던 자원은 환원되지 못하고 계속 메모리를 차지하고 있게되는 불상사가 있다.
(다운로드중 인터넷이 끊기면 파일이 fdsragds.434.fds이런 형식으로 남는다)
Stop 플래그
stop 플래그 방식은 조건식에 stop를 넣어서 false인경우 자원반환 코드를 넣는방식이다.
public void run() {
while(!stop){ 반복할 코드 }
스레드가 사용한 자원 정리 코드
}
}
interrupt()
스레드가 일시정지 상태에 있을때 interruptedException예외를 발생시키는 역할로 해당 에러가 발생했을때 run메서드의 정상종료를 꾀할수 있다. (일시정지가 아닌 실행,실행대기 상태에서 interrupt() 사용시 예외가 발생하지 않고 이후에 일시정지 상태가 되어야 예외가 발생한다.)
interrupt가 정상적으로 작동된경우 true를 리턴하는데 이것을 이용하여
Thread.interrupt(); //interruptedException예외발생
데몬스레드(daemon)
주 스레드의 작업을 돕는 보조적인 스레드로 주 스레드 종료시 보조 스레드(데몬스레드)도 종료된다. (종속된다고 생각하면 된다)
주스레드 : 워드, 영상 플레이어, JVM
보조 스레드 : 워드의 자동저장, 동영상 및 음악재생, GC
단, start() 메서드 호출한뒤 setDaemon(true)를 호출하면 ILLEGAlThreadStateException발생
ILLEGAlThreadStateException - 쓰레드 에서 Start로 작업중인데 Start를 한번 더 실행한경우 ("이미 실행중인데 또 실행하라고?")
threadA.setDeamon(true); //threadA를 데몬(보조)스레드로 변경
threadA.isDaemon(); //threaA가 데몬스레드인지 확인용 (데몬일경우 true 리턴)
스레드 그룹
관련된 스레드를 묶어서 한번에 관리할목적으로 이용된다.
또한 스레드 그룹에서 제공하는 여러 메서드가 있는데 한가지 예시로 interrupt()메서드를 호출하면 스레드 전체의 제어 및 정보를 얻어올수 있기 때문이다.
(스레드 그룹의 interrupt() 메서드 호출시 각 스레드마다 interrupt() 메서드를 내부적으로 호출해준다.)
interrupt - 스레드에게 작업을 멈춰달라고 요청하는것
여담으로 JVM은 실행될때 system이름의 스레드 그룹을 생성하고 JVM이 운영에 필요한 스레드를 생성하여 system그룹에 포함시킨다. (Finalizer(GC관련 스레드)가 있다.)
스레드그룹이 갖고있는 주요 메서드
코드 | 설명 |
(int형) activeCount() | 현재 또는 하위그룹에서 활동중인 모든 스레드의 수를 리턴 |
(int형) activeGroupCount() | 현재 그룹에서 활동중인 모든 하위그룹의 수를 리턴 |
(int형) getMaxPriority() | 현재 그룹에 속한 스레드가 가질수 있는 최대 우선순위를 리턴 |
(bool형) isDestoryed() | 현재 그룹이 삭제되었는지 여부를 리턴 |
(bool형) isDaemon() | 현재 그룹이 데몬그룹(보조그룹)인지 여부를 리턴 |
(bool형) parentOf(ThreadGroup g) | 현재 그룹이 매개값으로 지정한 스레드 그룹의 부모인지 여부를 리턴 |
(void형) checkAccess() | 스레드가 스레드 그룹을 변경할 권한여부 체크(관리자인지?) 권한이 없으면 SecurityException을 발생 |
(void형) destory() | 현재 및 하위그룹을 모두 삭제 (그룹내에 포함된 스레드가 종료상태가 되는게 전제조건) |
(void형) setMaxPriority(int pri) | 현재 그룹에 속한 스레드가 가질수 있는 최대 우선순위를 설정 |
(void형) setDaemon(boolean daemon) | 현재 그룹을 데몬그룹(보조그룹)으로 설정한다 |
(void형) list() | 현재 그룹에 포함된 스레드와 하위그룹에 대한 정보를 출력 |
(void형) interrupt() | 현재 그룹에 포함된 모든 스레드를 interrupt()한다. |
(String형)getName() | 현재 그룹의 이름을 리턴 |
(ThreadGroup형)getParent() | 현재그룹의 부모그룹을 리턴한다. |
스레드 그룹 생성
자신이 원하는 스레드 그룹을 만들어 관리하고 싶다면 하단의 코드를 활용하여 스레드 그룹을 생성하면 된다.
//parent - 부모로 만들 스레드그룹 이름
//name - 생성할 스레드그룹 이름 작성
ThreadGroup tg = new ThreadGroup(String name); //메인스레드에서 생성시 main의 하위스레ㅡ
ThreadGroup tg = new ThreadGroup(ThreadGroup parent, String name);
//스레드 그룹을 매개값으로 Thread 생성자
//target - Runnable 타입 구현객체
//name - 생성할 스레드의 이름
//stackSize - JVM이 할당할 stack의 크기
Thread t = new Thread(ThreadGroup group, Runnable targer);
Thread t = new Thread(ThreadGroup group, Runnable targer, String name);
Thread t = new Thread(ThreadGroup group, Runnable targer, String name, long stackSzie);
Thread t = new Thread(ThreadGroup group, String name);
모든 스레드는 반드시 하나의 스레드 그룹에 포함되며 명시적으로 스레드 그룹에 포함시키지 않으면 자신을 생성한 스레드 그룹에 속하게된다.(디폴트)
EX : 우리가 생성하는 작업스레드는 대부분 main스레드가 생성한다(main 스레드 그룹에 속한다)
또한 현재 사용중인 스레드 그룹의 이름을 확인하려면 다음과 같은 코드를 작성한다.
//현재 스레드가 속한 스레드그룹의 이름
ThreadGroup group = new Thread.currentThread().getThreadGroup();
String a = group.getName();
//프로세스내에서 실행하는 모든 스레드의 대한 정보 (그룹명, 소속그룹)
스레드 그룹 예시 코드
//자동 저장용 코드
public class AutoSaveThread extends Thread {
public void save(){ //메서드 생성
System.out.println("저장완료");
}
@Override
public void run(){
while(true){ //무한반복
try{
Thread.sleep(1000); //1000밀리초 = 1초 딜레이
} catch (InterruptedException e) {
break; //while 탈출
}
save(); //상단의 save 호출
}
}
}
스레드 그룹 예시코드 2
//스레드 정보 얻기 코드
public class ThreadInfoExample {
public static void main(String[] args) {
AutoSaveThread autoSaveThread = new AutoSaveThread(); //스레드 생성자
autoSaveThread.setName("AutoSaveThread"); //스레드명 변경
autoSaveThread.setDaemon(true); //데몬(보조)스레드 선언
autoSaveThread.start();
//autoSaveThread.isDaemon(); //autoSaveThread 데몬스레드인지 확인용 (true 리턴)
Map<Thread. StackTraceElement[]> map = Thread.getAllStackTraces();
Set<Thread> threads = map.keySet();
for(Thread thread : threads) { //Thread 전체 루핑
System.out.println("Name: " + thread.getName() +
((thread.isDaemon())?"(데몬)": "(주)"));
System.out.println("\t" + "소속그룹: " + thread.getThreadGroup().getName());
System.out.println();
}
}
}
(루핑 - 요소 전체를 반복하라)
스레드 풀
멀티스레드, 병렬작업이 많아질수록 스레드 증가, 이에 따른 스케쥴링으로 메모리 사용량이 늘어나는데 이를 내버려뒀다간 스레드 폭증으로 PC가 뻗어버리게된다.
이를 막기 위해선 스레드풀이란 기능을 활용해야한다.
스레드풀 - 작업 처리에 사용되는 스레드를 제한된 개수만큼 정해놓고 작업 큐(대기열)에 들어오는 작업을 하나씩 스레드가 처리한후 작업처리가 끝난 스레드는 다시 작업 큐(대기열)에 들어오는작업을 가져와 처리한다.
(손님이 들어오는대로 다 받으면 식당 내부에서 난리가 날테니 줄세워놓고 한명씩 들어오라 하는것)
자바는 스레드풀 생성을 위하여 java.util.concurrent 패키지에 ExecutorService인터페이스 및 Executor 클래스를 제공한다
스레드풀 생성
ExecutorService 구현객체는 Executor 클래스의 여러메서드중 하단의 양식을 사용하여 생성한다.
//스레드풀 생성 1번
//양식 : newCachedThreadPool()
//초기 스레드 및 코어스레드 : 0개, 최대스레드 : interger.MAX_VALUE
ExecutorService executorService = Executors.newCachedThreadPool();
//스레드풀 생성 2번
//양식 : newFixedThreadPool(int nThreads)
//초기 스레드 : 0개, 코어스레드 : nThreads개, 최대스레드 : nThreads
ExecutorService executorService = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors() //JVM에서 현재 이용가능한 코어갯수를 리턴
//CMD사용하기 - Runtime.getRuntime()
//JVM에서 현재 이용가능한 코어갯수를 리턴 - availableProcessors()
)
//스레드풀 생성 3번 (Executor 클래스 미사용시)
//초기스레드는 0개이다.
//ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)
ExecutorService threadPool = new ThreadPoolExecutor(
3, //코어 스레드 개수 -> corePoolSize(생성할 개수)
100, //최대 스레드 개수 -> maximumPoolSize(생성할 최대 개수)
120L, //놀고있는 시간으로 초과시 삭제 -> keepAliveTime(유지 시간)
TimeUnit.SECONDS, //놀고있는 시간단위 (초) -> TimeUnit unit
new SyschronousQueue<Runnable>() //작업 큐 -> BlockingQueue<Runnable> workQueue
);
corePoolSize -기본풀 사이즈로 최소 실행할 쓰레드의 숫자(코어스레드)
maximumPoolSize - 최대 지원 스레드 숫자(최대스레드)
keepAliveTime - 스레드 미사용시 제거대기시간
TimeUnit unit - keepAliveTime값의 시간단위
BlockingQueue <Runnable> workQueue - 요청된 작업들이 저장될 큐 (corePoolSize보다 작업스레드가 많을때 남는쓰레드도 없는경우 workQueue에서 대기 -> 마린 3개를 한번에 찍으면 2개는 생산대기중이 되는것과 같은개념이다.)
RejectedExecutionHandler handler - 작업요청 거부시 처리할 핸들러
코어 스레드 수 - 스레드 수가 증가된후 미사용 스레드를 스레드풀에서 제거할때 최소한 유지해야할 스레드수
초기 스레드 수 - ExecutorService객체가 생성될때 기본적으로 생성되는 스레드의 숫자 (디폴트는 0)
최대 스레드 수 - 스레드풀에서 관리하는 최대 스레드 수(최대로 가용할수 있는 스레드수)
newCachedThreadPool 로 생성된 경우 스레드 개수보다 작업 개수가 많으면 새 스레드를 생성하며 스레드가 추가되었을때 1분만 작업이 없으면 추가된 스레드를 종료 및 풀에서 제거한다.
newFixedThreadPool(int nThreads) 로 생성된경우 nThreads의 값만큼 작업을 처리하지 않고 놀고있어도 스레드 개수는 줄지않는다. (nThreads가 5인경우 Idle상태더라도 최소5개의 스레드는 들고간다)
스레드풀의 종료
스레드풀의 스레드는 데몬스레드(보조스레드)가 아니기에 main스레드가 종료되도 작업 마무리를 위해 잔존한다 ->
어플리케이션의 완전한 종료를 위해선 스레드풀도 종료시켜 하위스레드도 전부 종료시켜야한다.
(리턴 타입) 메서드명 (매개변수) | 설명 |
(void형) shutdown() | 현재 처리중인 작업, 작업 큐에 대기중인 작업을 처리 후 종료 -> 일반적인 종료 |
(List<Runnable>형) shotdownNow() | 현재 작업처리중인 스레드를 interrupt하여 작업중지 시도, 스레드풀 종료 (리턴값은 작업큐에 있는 미처리된 작업목록) -> 강제종료 |
(bool형) awaitTermination(long timeout,TimeUnit unit) | shutdown() 메서드 호출 이후 모든 작업처리시간을 timout 시간 내에 완료하면 true, 그렇지 않으면 false를 리턴 |
(timeout - 120, TimeUnit - 초, 분, 시 EX: awaitTermination(50,MINUTES) -> 50분까지 끝내면 true, 그렇지 않으면 false)
ExecutorService.shoutdown(); //일반적인 종료
ExecutorService.shoutdownNow(); //강제 종료
executor.awaitTermination(30, TimeUnit.SECONDS); //30초안에 못끝내면 false;
스레드풀 작업생성
작업생성은 하나의 작업은 Runnable 또는 Callable 구현클래스로 표현한다
Runnable 예시코드 - 리턴값 없음 (void)
Runnable task = newRunnable() {
@Override
public void run() {
//작업할 내용
}
}
Callable 예시코드 - 리턴값 있음
- <T>는 제네릭의 표시로 예비 데이터 타입을 뜻한다
- 제네릭은 클래스나 메소드에서 사용할 내부 데이터 타입을 컴파일 시에 미리 지정하는 방법
Callable<T> task = new Callable<T>() {
@Override
public T call() throws Exception{
//스레드가 처리할 내용
return T;
}
}
스레드풀 작업처리요청
ExecutService의 작업 큐에 Runnable 또는 Callable 객체를 넣는행위를 뜻한다. (유닛생산건물 대기열에 유닛생산을 추가버튼을 누르는것 생각하면 된다)
(리턴 타입) 메서드명 (매개변수) | 설명 |
(void형) execute(Runnable command) | - Runnable을 작업큐에 저장 - 작업 처리결과를 받지 못한다 (void형) |
(Future<?>형) submit(Runnable task) (Future<V>형) submit(Runnable task, V result) (Future<V>형) submit(Callable<V> task) |
- Runnable 또는 Callable을 작업큐에 저장 - 리턴된 Future를 통하여 작업처리결과를 얻을수 있다. |
execute() 메서드는 작업처리도중 예외가 발생하면 스레드가 종료되고 해당 스레드는 스레드 풀에서 제거하며 새로운 스레드를 생성한다.(처리결과를 받지 못함과 동시에 삭제)
submit() 메서드는 작업처리도중 예외가 발생하여도 스레드는 종료되지않고 계속 재사용된다.
여기서 submit의 리턴타입인 Future는 블로킹 방식으로 작동하며 최종결과를 얻는데 사용된다.
(작업요청시 요청한 순서대로 작업처리가 완료되는것은 아니다 -> 코드 100개있는 스레드랑 코드10줄있는 스레드랑 속도가 다르다 -> 작업처리 완료된것만 확인하는건 CompletionService이용)
블로킹 방식의 작업완료 통보
블로킹이란 작업결과가 아닌 작업이 완료될떄까지 기다렸다가 최종결과를 얻는데 사용된다 (즉, 중간 산출물정도라 이해하면 되며 이러한 블로킹 기능 때문에 Future객체를 지연완료 객체라고 칭한다.)
리턴타입 | 메서드명 (매개변수) | 설명 | |||||
Future<?> | submit(Runnable task) | - Runnable 또는 Callable를 작업 큐에 저장 - 리턴된 Future를 통해 작업 처리 결과를 얻는다. |
|||||
Future<V> | submit(Runnable task,Integer result) | ||||||
Future<V> | submit(Callable<String> task) |
이것을 좀더 자세히 살펴보겠다
리턴타입인 future를 통하여 작업 처리결과를 얻는다고 하는데 submit을 리턴부분(future)과 메서드 부분을 나눠 설명하겠다.
첫번째로 submit의 리턴부분인 future가 갖고있는 get() 메서드를 설명한게 하단의 표이며 V는 (상단의) submit(Runnable task, V result)의 V형 또는 submit(Callable<V> task)의 V형이다
리턴타입 | 메서드명(매개변수) | 설명 | ||
V타입 | get() | 블로킹 되어있다 처리결과V를 리턴 |
||
V타입 | get(long timeout, TimeUint unit) | timeout 안에 작업 완료시 V를 리턴 timeout 초과시 TimeoutException 발생 |
두번째로 submit메서드를 좀더 상세히 설명하자면 다음과 같다
작업처리도중 예외는 동일하며 리턴타입이 다른형식으로 나타나는걸 확인할수 있다.
메서드 | 리턴타입 | 작업 처리도중 예외발생 |
submit(Runnable task) -> 리턴값이 없는 작업완료통보 | future.get() -> null | future.get() -> 예외발생 |
submit(Runnable task, Integer result) | future.get() -> int 타입 값 | |
submit(Callable<String> task) |
future.get() -> String 타입 값 |
future를 이용한 블로킹 방식은 작업을 처리하는 스레드가 작업을 완료하기 전까진 get메서드는 블로킹 된다.
작업결과를 도출하는걸 봤으니 결과를 확인하는 방법을 알아보자
여기서 Runnable은 리턴값 없으며
future객체는 작업결과를 얻기 위한 get 메서드 이외에도 다음과 같은 메서드를 제공한다
리턴타입 | 메서드명(매개변수) | 설명 | ||
bool | cancel(boolean mayInterruptlfRunning) | 작업처리가 진행중일경우 취소시킴 | ||
bool | isCancelled() | 작업취소가 되었는지 여부 | ||
bool | isDone() | 작업처리가 완료되었는지 여부 |
cancel()
작업이 시작되기 전 - mayInterruptIfRunning와 무관하게 작업취소후 true리턴 (시작도 안했으니 true(취소작업가능))
작업진행중 - mayInterruptIfRunning가 ture일경우만 작업스레드 interrupt (인터럽트가 떠야만 멈춤가능)
작업완료, 모종의 이유로 취소불가시 - false 리턴 (이미 끝낸걸, 내권한 밖인걸 취소하라고? false(취소작업 불가))
iscancelled()
작업이 완료되기전에 취소가 되었을경우만 true (현재 작업중인것만 취소가능)
isdone()
정상완료 뿐만아니라 예외, 취소등 작업이 완료되기만하면 true(완료는 했는데 정상완료인지 모른다..)
리턴값이 없는 작업완료 통보 (Runnable)
Runnable객체로 생성시 리턴값이 없으며 하단은 양식이다.
Runnable task = new Runnable() {
@Override
public void run() { //리턴값 없기에 void
//실행내용
}
}
리턴값이 없는 작업처리요청은 submit(Runnable task)메서드를 활용한다 (상단의 표에 표시해놓았다).
여기서 리턴값이 없는데 future를 리턴하는 이유가 무엇이냐고 물을수 있는데 이것은 작업처리도중 예외가 발생했는지 확인하기 위함이다.
//void지만 작업처리 도중 예외가 발생했는지 여부를 확인하기 위해 future 객체를 받는다
Future future = ExecutorService.submit(task);
리턴값이 있는 작업완료 통보 (Callable)
스레드풀의 스레드가 작업을 완료한후 애플리케이션이 처리결과를 얻어야한다면 작업객체를 Callable로 생성한다.
Callable<T> task = new Callable<T>() {
@Override
public T Call() throws Exception { //T타입 , 예외발생여부
//실행내용
return T;
}
}
작업처리요청은 submit()메서드를 실행하고나면 T타입의 값을 리턴하면 Future<T>의 get 메서드는 블로킹 해제,T타입 값 리턴한다.
//submit메서드는 작업큐에 Callable객체를 저장, Future<T>를 리턴한다.
//여기서 T는 call메서드가 리턴하는 타입
Future<T> future = executorService.submit(task);
작업처리결과를 외부 객체에 저장
작업이 완료된것을 외부 객체에 저장해야 하는경우도 종종 있다.
스레드가 작업처리를 완료하고 외부 Result객체에 작업결과를 저장하면 애플리케이션이 이것을 활용해 작업이 가능하다.
class Task implements Runnable {
Result result; //외부 객체 사용을 위해 선언
Task(Result result) { this.result = result; } //외부 Result 객체를 필드에 저장
@Override
public void run() {
//작업코드
//처리 결과를 result에 저장
}
}
작업완료순으로 통보
작업을 요청한 순서대로 작업처리가 완료되는건 아니며 드문드문 완료된 스레드를 찾는 작업은 CompletionService의 poll(), take()메서드를 사용한다
리턴타입 | 메서드명(매개변수) | 설명 | ||
Future<V> | poll() | 완료된 작업의 Future를 가져옴 완료된 작업이 없을경우 null리턴 |
||
Future<V> | poll(long timeout, TimeUnit unit) | 완료된 작업의 Future를 가져옴 완료된 작업이 없을경우 timeout까지 블로킹후 그래도 없으면 null 리턴 |
||
Future<V> | take() | 완료된 작업의 Future를 가져옴 완료된 작업이 없을경우 있을때까지 블로킹 |
||
Future<V> | submit(Callable<V> task) | 스레드풀에 Callable작업 처리요청 (리턴 있음) | ||
Future<V> | submit(Runnable task, V result) | 스레드풀에 Runnable작업 처리요청 (리턴 있음) |
CompletionService의 구현클래스는 ExecutorCompletionService<V>이다.
//CompletionService 양식
CompletionService<V> completionService = new ExecutorCompletionService<V>{
executorService
}
콜백(callback)방식의 작업 완료통보
콜백 - 애플리케이션 스레드에게 작업처리를 요청한후 스레드가 작업을 완료하면 특정 메서드를 자동실행하는 기법
블로킹 방식과 콜백방식 비교
블로킹은 앞서 "작업이 완료될떄까지 기다렸다가 최종결과를 얻는데 사용된다"라고 설명하였다
콜백은 블로킹과 다르게 작업처리 요청후 다른기능을 수행할수 있다. -> 작업처리가 완료되면 콜백메서드가 자동적으로 실행되어 결과를 알수 있기 때문이다.
콜백메서드는 CompletionHandler를 사용하며 이것은 completed()와 failed() 메서드가 있다.
completed는 정상적으로 완료될경우 사용하며 failed는 비정상적으로 작동시 사용된다
하단의 코드를 참조하길 바란다.
//CompletionHandler<V,A>에서 V는 결과값의 타입, A는 첨부값의 타입이다.
//첨부값은 콜백 메서드에 결과값 이외에 추가적으로 전달하는 객체라고 이해하면 된다.
CompletionHandler<V,A> callback = new CompletionHandler<V,A>() {
//정상 완료시 completed
@Override
public void completed(V result, A attachment){
}
//작동중 문제발생 failed
@Override
public void failed(V exc, A attachment){
}
};
//사용예시
private CompletionHandler<Integer,void> callback = new CompletionHandler<Integer, void>() {
//정상 완료시 completed (A가 필요없을경우)
@Override
public void completed(V result, void attachment){
}
try {
callback.completed(result,null); //정상처리 했을겨우 호출
} catch (NumberFormatExeception e) {
callback.failed(e.null); //예외 발생시 호출
}
'JAVA' 카테고리의 다른 글
Call by value와 Call by reference (0) | 2024.02.24 |
---|---|
DocumentBuilderFactory와 예제 (0) | 2024.02.18 |
프로세스와 스레드 (0) | 2023.03.07 |
동일성(identity)과 동등성(equality) (0) | 2023.02.26 |
예외에 관하여 (0) | 2023.02.24 |