스레드 그룹은 관련된 스레드를 묶어서 관리할 목적으로 이용한다. JVM이 실행되면 system 스레드 그룹을 만들고, JVM 운영에 필요한 스레드들을 생성해서 system 스레드 그룹에 포함시킨다. 그리고 system의 하위 스레드 그룹으로 main을 만들고 메인 스레드를 main 스레드 그룹에 포함시킨다.
스레드는 반드시 하나의 스레드 그룹에 포함되고, 메서드를 통해서 스레드의 그룹을 지정해주지 않으면 기본적으로 자신을 생서한 스레드와 같은 스레드 그룹에 속하게 된다.
ThreadGroup group = Thread.currentThread().getThreadGroup();
String groupName = group.getName();
static method인 getAllStackTraces()
를 이용하면 현재 애플리케이션에서 실행 중인 모든 스레드의 정보를 Map
형태로 얻어올 수 있다.
public class AutoSaveApp {
public static void main(String[] args) {
AutoSaveThread autoSaveThread = new AutoSaveThread();
autoSaveThread.setDaemon(true);
autoSaveThread.start();
try{
Thread.sleep(3000);
}catch (InterruptedException e){}
Map<Thread, StackTraceElement[]> threadMap = Thread.getAllStackTraces();
Set<Thread> threadSet = threadMap.keySet();
for (Thread t : threadSet){
System.out.println("thread name : " +t.getName());
System.out.println("thread group : " +t.getThreadGroup().getName());
System.out.println("is daemon? : " +t.isDaemon());
}
System.out.println("main thread now shutting down. Daemon also stops");
}
}
아래 코드만 출력한 결과는 다음과 같다.
thread name : Attach Listener
thread group : system
is daemon? : true
thread name : Finalizer
thread group : system
is daemon? : true
thread name : Monitor Ctrl-Break
thread group : main
is daemon? : true
thread name : Thread-0
thread group : main
is daemon? : true
thread name : main
thread group : main
is daemon? : false
thread name : Signal Dispatcher
thread group : system
is daemon? : true
thread name : Reference Handler
thread group : system
is daemon? : true
GC를 담당하는 Finalizer 스레드를 비롯한 일부 스레드들이 system 그룹에 속하고, main은 main스레드 그룹에 속해있다.
ThreadGroup을 생성하는 생성자는 두 가지가 있다.
ThreadGroup(String name);
ThreadGroup(ThreadGroup parent, String name);
첫 번째 생성자는 이름만 지정하는 것이고, 두 번째 생성자는 부모 스레드 그룹을 지정하는 것이다. 부모 스레드 그룹을 명시하지 않고 스레드 그룹을 생성하면, 이 스레드 그룹을 생성한 스레드의 하위 스레드 그룹이 된다. 예를 들어 main 스레드에서 new ThreadGroup("threadGroup1")
을 호출한 뒤 getThreadGroup().getName()
의 리턴값은 "main"
이 된다.
새로운 스레드를 스레드 그룹에 포함 시키려면 스레드 클래스의 생성자 오버로딩을 이용하면 된다. 네 가지가 있다.
Thread(ThreadGroup group, Runnable target);
Thread(ThreadGroup group, Runnable target, String name);
Thread(ThreadGroup group, Runnable target, String name, long stackSize);
Thread(ThreadGroup group, String name);
stackSize
는 JVM이 이 스레드에 할당할 스택 사이즈를 의미한다.
interrupt()
스레드 그룹으로 스레드를 관리하면 어떤 점이 좋을까? 스레드 그룹에서 제공하는 interrupt()
를 호출하면 그룹 내 모든 스레드를 일괄적으로 interrupt
할 수 있다. 스레드 그룹의 interrupt()
는 내부적으로 그룹에 포함된 모든 스레드의 interrupt()
를 호출한다.
다만 앞서 학습한 것처럼 interrupt()
는 스레드가 실행 대기 또는 실행 중이라면 예외를 발생시키지 않으므로 그룹에서 interrupt()
해도 모든 스레드가 종료되지 않을 수 있다. 따라서 안전한 종료를 위해서는 개별 스레드가 예외 처리를 해야 한다. 아래는 ThreadGroup.interrupt()
코드이다.
public final void interrupt() {
int ngroupsSnapshot;
ThreadGroup[] groupsSnapshot;
synchronized (this) {
checkAccess();
for (int i = 0 ; i < nthreads ; i++) {
threads[i].interrupt();
} // 각 스레드의 interrupt() 호출
ngroupsSnapshot = ngroups;
if (groups != null) {
groupsSnapshot = Arrays.copyOf(groups, ngroupsSnapshot);
} else {
groupsSnapshot = null;
}
}
for (int i = 0 ; i < ngroupsSnapshot ; i++) {
groupsSnapshot[i].interrupt();
}
}
이 외에도 그룹에 속한 모든 스레드를 제거하는 destroy()
, 스레드를 추가하는 add()
, 하나의 스레드만 제거하는 remove()
등 다양한 메서드가 제공된다. 다음 예제에서는 스레드 그룹을 통해 일괄적으로 스레드를 종료시키는 기능을 구현해본다.
package org.java.chap12.thread_group;
public class AThread extends Thread {
public AThread(ThreadGroup parent, String threadName){
super(parent,threadName);
}
@Override
public void run() {
while(true){
try{
System.out.println(getName() + " is running");
Thread.sleep(1000);
} catch (InterruptedException e){
System.out.println(this.getName()+ "is interrupted");
break; // interrupted로 예외가 발생하면 catch해서 break }
}
System.out.println(this.getName() + " stops");
}
}
package org.java.chap12.thread_group;
public class ThreadGroupTestApp {
public static void main(String[] args) {
ThreadGroup threadGroup = new ThreadGroup("myGroup");
AThread threadA = new AThread(threadGroup, "threadA");
AThread threadB = new AThread(threadGroup, "threadB");
threadA.start();
threadB.start();
System.out.println("main thread list");
ThreadGroup mainGroup = Thread.currentThread().getThreadGroup();
mainGroup.list();
System.out.println();
try{
Thread.sleep(3000);
} catch (InterruptedException e){}
System.out.println("thread group interrupts all threads");
threadGroup.interrupt();
}
}
main thread list
threadB is running
threadA is running
java.lang.ThreadGroup[name=main,maxpri=10]
Thread[main,5,main]
Thread[Monitor Ctrl-Break,5,main]
java.lang.ThreadGroup[name=myGroup,maxpri=10]
Thread[threadA,5,myGroup]
Thread[threadB,5,myGroup]
threadA is running
threadB is running
threadA is running
threadB is running
thread group interrupts all threads
threadBis interrupted
threadB stops
threadAis interrupted
threadA stops
코드 수행 결과.
AThread는 Thread의 생성자 오버로딩을 통해서 생성자 호출 시 스레드 그룹과 스레드 이름으로 초기화된다.
앱에서 threadA와 threadB는 "myGroup"이라는 이름을 가진 스레드 그룹에 속하게 된다. 또한 myGroup 스레드그룹은 스레드그룹을 지정하지 않은 채로 main에서 생성되었으므로 main의 하위 그룹이 된다.
AThread에서 InterruptedException
예외를 처리하고 있으므로 3초 뒤 정상적으로 모든 스레드가 myGroup의 interrupt()
에 의해서 정상적으로 종료된다.
이번 장에서는 스레드를 그룹으로 묶어 관리하는 스레드 그룹에 대해 알아보았다.
다음 장에서는 스레드의 개수를 제한하고 작업 순서를 관리하여 애플리케이션의 과부화를 막을 수 있도록 도와주는 기능인 스레드풀에 대해 알아보도록 한다.