ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [자바의 정석] Ch.13 쓰레드
    스터디플래너/공부하기 2022. 7. 24. 10:03

    6. 쓰레드 그룹(thread group)

     서로 관련된 쓰레드를 그룹으로 다루기 위한 것으로 쓰레드를 그룹으로 묶어서 관리할 수 있다. 쓰레드 그룹에 다른 쓰레드 그룹을 포함시킬 수 있고 자신이 속한 쓰레드 그룹이나 하위 쓰레드 그룹을 변경할 수 있지만 다른 쓰레드 그룹의 쓰레드를 변경할 수 없다.

     자바 어플리케이션이 실행되면 JVM은 main 쓰레드 그룹과 system 쓰레드 그룹을 만들고 JVM운영에 필요한 쓰레드들을 생성해서 쓰레드 그룹에 포함시킨다. 모든 쓰레드 그룹은 main 쓰레드 그룹의 하위 쓰레드 그룹이 되고, 쓰레드 그룹을 지정하지 않고 생성한 쓰레드는 자동적으로 main쓰레드 그룹에 속하게 된다.

    Thread 클래스의 getThreadGroup()메소드를 이용하면 어떤 쓰레드 그룹에 속해있는지 알 수 있다. ThreadGroup에 자주 쓰이는 생성자와 메소드이다.

    생성자/메소드 설명
    ThreadGroup(String name) 지정된 이름의 새로운 쓰레드 그룹을 생성
    ThreadGroup(ThreadGroup parent, String name) 지정된 쓰레드 그룹에 포함되는 새로운 쓰레드 그룹을 생성
    int activeCount() 쓰레드 그룹에 포함된 활성상태에 있는 쓰레드의 수를 반환
    int activeGroupCount() 쓰레드 그룹에 포함된 활성상태에 있는 쓰레드 그룹의 수를 반환
    void checkAccess() 현재 실행중인 쓰레드가 쓰레드 그룹을 변경할 권한이 있는지 체크. 만일 권한이 없다면 SecurityException을 발생시킨다.
    void destroy() 쓰레드 그룹과 하위 쓰레드 그룹까지 모두 삭제한다. 단, 쓰레드 그룹이나 하위 쓰레드 그룹이 비어있어야 한다.
    int enumerate(Thread[] list)
    int enumerate(Thread[] list, boolean recurse)
    int enumerate(ThreadGroup[] list)
    int enumerate(ThreadGroup[] list, boolean recurse)
    쓰레드 그룹에 속한 쓰레드 또는 하위 쓰레드 그룹의 목록을 지정된 배열에 담고 그 개수를 반환.
    두 번째 매개변수인 recurse의 값을 true로 하면 쓰레드 그룹에 속한 하위 쓰레드 그룹에 쓰레드 또는 쓰레드 그룹까지 배열에 담는다.
    int getMaxPriority() 쓰레드 그룹의 최대 우선순위를 반환
    String getName() 쓰레드 그룹의 이름을 반환
    ThreadGroup getParent() 쓰레드 그룹의 상위 쓰레드그룹을 반환
    void interrupt() 쓰레드 그룹에 속한 모든 쓰레드를 interrupt
    boolean isDaemon() 쓰레드 그룹이 데몬 쓰레드그룹인지 확인
    boolean isDestroyed() 쓰레드 그룹이 삭제되었는지 확인
    void list() 쓰레드 그룹에 속한 쓰레드와 하위 쓰레드그룹에 대한 정보를 출력
    boolean parentOf(ThreadGroup g) 지정된 쓰레드 그룹의 상위 쓰레드그룹인지 확인
    void setDaemon(boolean daemon) 쓰레드 그룹을 데몬 쓰레드그룹으로 설정/해제
    void setMaxPriority(int pri) 쓰레그 그룹의 최대우선순위를 설정

    책에서는 Thread의 쓰레드 그룹과 관련된 메서드라고 void uncaughtException(Thread t, Throwable e) 메소드를 소개했는데 찾아보니 Thread 클래스가 아니라 ThreadGroup 클래스의 메소드이다. 쓰레드 그룹의 쓰레드가 처리되지 않은 예외에 의해 실행이 종료되었을 때, JVM에 의해 이 메서드가 자동적으로 호출된다.

     

    자바의 정석 교재에 ThreadEx9 예제이다. ThreadGroup을 두 개(Group1, Group2)를 만들고 Group1에 SubGroup1을 하위 쓰레드 그룹으로 추가했다. 각각 ThreadGroup1의 th1, SubGroup1의 th2, Group2의 th3을 순서대로 실행했을 때의 결과값이다. 가장 먼저 main 쓰레드가 있고, 한 칸 들여쓰기로 main 쓰레드그룹에 속하는 ThreadGroup1과 th1, 그리고 한 칸 더 들여쓰기로 SubGroup1과 th2, 그 다음 Group1과 같은 단계로 Group2와 th3이 출력되었다. 사실 코드와 결과값이 매치가 잘 안 되어서 정리하려고 캡쳐했는데 보다보니 이해가 됐다.

     

     

     

    7. 데몬 쓰레드(daemon thread)

    데몬 쓰레드는 데몬 쓰레드가 아닌 쓰레드의 작업을 도와 보조 역할을 수행하는 쓰레드이다. 일반 쓰레드가 없으면 데몬쓰레드가 존재할 이유가 없으므로 종료되면 데몬 쓰레드도 강제적으로 종료된다. 자바의 가비지컬렉터, 워드프로세서의 자동저장, 화면 자동갱신이 데몬쓰레드의 예이다.

     데몬 쓰레드는 무한루프와 조건문을 이용하여 실행 후 대기하고 있다가 특정 조건을 만족하면 작업을 수행하고 다시 대기하도록 작성한다. 쓰레드의 작성과 실행방법은 일반 쓰레드와 동일하며 쓰레드를 생성한 다음 setDaemon(true)를 호출하면 된다. 데몬 쓰레드가 생성한 쓰레드는 자동적으로 데몬 쓰레드가 된다. setDaemon()은 start()를 호출하기 전 실행해야 한다. 그렇지 않으면 IllegalThreadStateException이 발생한다.

     

     자바의 정석 교재에 있는 Thread10 예제이다. 작업파일을 자동으로 저장한다고 출력하는 autoSave()는 메소드는 데몬 쓰레드로 autoSave라는 변수값이 true여야 호출할 수 있다. 그리고 autoSave는 전역변수로 false값을 갖다가 5초가 지나면 true가 된다. 그래서 5초까지는 숫자만 입력했고 5초 뒤부터 3초 간격으로 자동 저장 메세지를 출력했다. 그리고 10초가 지나면 반복문이 종료되므로 데몬 쓰레드도 함께 종료되었다. 한번 데몬 쓰레드로 선언하지 않으면 어떻게 되나 확인해봤다.

    설명처럼 데몬쓰레드는 일반 쓰레드가 끝나면 함께 종료되는데 이 경우 setDaemon()을 호출하지 않았더니 메인 메소드의 메인쓰레드가 종료되었어도 계속 3초 간격으로 자동 저장 메세지를 출력했다.

    start()로 쓰레드를 실행하기 전 setDaemon()을 호출해줘야 한다길래 두 순서를 바꾸면 어떻게 되나 바꿔봤더니 예외처리가 되었다.

     

    getAllStackTraces()를 이용하면 실행 중, 대기상태의 모든 쓰레드 호출스택을 출력할 수 있다. 책에서는 총 6개의 쓰레드가 실행 중이거나 대기상태이지만 나의 PC에서는 총 9개의 쓰레드가 실행 중이거나 대기상태였다. 책에서는 t.getThreadGroup().getName()을 이용하여 group의 이름을 간단하게 작성했지만 나의 경우 2번째 쓰레드인 main쓰레드가 갖고있는 group값이 null이라 불가피하게 getName()은 제외해야 했다. main클래스에 'throw Exception'을 빼먹은 걸 발견하고 다시 돌려봤지만 결과는 동일했다.

    //ThreadEx11.java
    [1] name : Notification Thread,  group : java.lang.ThreadGroup[name=system,maxpri=10], daemon : true
    
    [2] name : main,  group : null, daemon : false
    java.base@17.0.2/java.util.IdentityHashMap$KeySet.iterator(IdentityHashMap.java:977)
    java.base@17.0.2/java.util.Collections$SetFromMap.iterator(Collections.java:5686)
    java.base@17.0.2/jdk.internal.misc.TerminatingThreadLocal.threadTerminated(TerminatingThreadLocal.java:69)
    java.base@17.0.2/java.lang.Thread.exit(Thread.java:843)
    
    [3] name : Reference Handler,  group : java.lang.ThreadGroup[name=system,maxpri=10], daemon : true
    java.base@17.0.2/java.lang.ref.Reference.waitForReferencePendingList(Native Method)
    java.base@17.0.2/java.lang.ref.Reference.processPendingReferences(Reference.java:253)
    java.base@17.0.2/java.lang.ref.Reference$ReferenceHandler.run(Reference.java:215)
    
    [4] name : Signal Dispatcher,  group : java.lang.ThreadGroup[name=system,maxpri=10], daemon : true
    
    [5] name : Thread1,  group : java.lang.ThreadGroup[name=main,maxpri=10], daemon : false
    java.base@17.0.2/java.lang.Thread.sleep(Native Method)
    app//ThreadEx11_1.run(ThreadEx11.java:21)
    
    [6] name : Thread2,  group : java.lang.ThreadGroup[name=main,maxpri=10], daemon : false
    java.base@17.0.2/java.lang.Thread.dumpThreads(Native Method)
    java.base@17.0.2/java.lang.Thread.getAllStackTraces(Thread.java:1662)
    app//ThreadEx11_2.run(ThreadEx11.java:33)
    
    [7] name : Common-Cleaner,  group : java.lang.ThreadGroup[name=InnocuousThreadGroup,maxpri=10], daemon : true
    java.base@17.0.2/java.lang.Object.wait(Native Method)
    java.base@17.0.2/java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:155)
    java.base@17.0.2/jdk.internal.ref.CleanerImpl.run(CleanerImpl.java:140)
    java.base@17.0.2/java.lang.Thread.run(Thread.java:833)
    java.base@17.0.2/jdk.internal.misc.InnocuousThread.run(InnocuousThread.java:162)
    
    [8] name : Monitor Ctrl-Break,  group : java.lang.ThreadGroup[name=main,maxpri=10], daemon : true
    java.base@17.0.2/java.net.InetSocketAddress.<init>(InetSocketAddress.java:233)
    java.base@17.0.2/java.net.Socket.<init>(Socket.java:287)
    app//com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:44)
    
    [9] name : Finalizer,  group : java.lang.ThreadGroup[name=system,maxpri=10], daemon : true
    java.base@17.0.2/java.lang.Object.wait(Native Method)
    java.base@17.0.2/java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:155)
    java.base@17.0.2/java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:176)
    java.base@17.0.2/java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:172)

     

    8. 쓰레드의 실행제어

    ⓵ 쓰레드를 생성하고 start()를 호출하면 바로 실행되는 것이 아니라 실행대기열이 저장되어 자신의 차례가 될 때까지 기다려야한다. 실행대기열은 큐(queue)와 같은 구조로 먼저 실행대기열에 들어온 쓰레드가 먼저 실행된다.

    ⓶ 실행대기상태에 있다가 자신의 차례가 되면 실행상태가 된다.

    ⓷ 주어진 실행시간이 다되거나 yield()를 만나면 다시 실행대기상태가 되고 다음 차례의 쓰레드가 실행 상태가 된다.

    ⓸ 실행 중에 suspend(), sleep(), wait(), join(), I/O block에 의해 일시정지상태가 될 수 있다. I/O block은 입출력작업에서 발생하는 지연상태를 말한다. 사용자가 입력을 마치면 다시 실행대기 상태가 된다.

    ⓹ 지정된 일시정지시간이 다되거나(time-out), notify(), resume(), interrupt()가 호출되면 일시정지상태를 벗어나 다시 실행대기열에 저장되어 자신의 차례를 기다린다.

    ⓺ 실행을 모두 마치거나 stop()이 호출되면 쓰레드는 모두 소멸된다.

    상태 설명
    NEW 쓰레드가 생성되고 아직 start()가 호출되지 않은 상태
    RUNNABLE 실행 중 또는 실행 가능한 상태
    BLOCKED 동기화 블럭에 의해서 일시정지된 상태(lock이 풀릴 때까지 기다리는 상태)
    WAITING
    TIMED_WAITING
    쓰레드의 작업이 종료되지는 않았지만 실행가능하지 않은(unrunnable) 일시정지 상태.
    TIMED_WAITING은 일시정지시간이 지정된 경우를 의미한다.
    TERMINATED 쓰레드의 작업이 종료된 상태

     

    sleep(long millis) - 일정시간동안 쓰레드를 멈추게 한다.

    지정된 시간(천분의 일초 단위)동안 쓰레드를 일시정지시킨다. 지정한 시간이 지나고 나면, 자동적으로 다시 실행대기상태가 된다.

    static void sleep(long millis)
    static void sleep(long millis, int nanos)

    sleep()에 의해 일시정지 상태가 된 쓰레드는 지정된 시간이 다 되거나 interrupt()가 호출되면(InterruptException 발생) 실행대기 상태가 된다. 그래서 try~catch문으로 예외를 처리해줘야 한다. try~catch문을 포함해서 메소드를 새로 만들어 사용하기도 한다. sleep()은 현재 실행중인 쓰레드에 대해 작동한다. static으로 선언되어 있으며 Thread.sleep(2000)과 같이 사용한다.

     

    interrupt()와 interrupted() - 쓰레드의 작업을 취소한다.

    sleep()이나 join()에 의해 일시정지상태인 쓰레드를 깨워서 실행대기상태로 만든다. 해당 쓰레드에서는 InterruptedException이 발생함으로써 일시정지상태를 벗어나게 만든다.

    void interrupt()				//쓰레드의 interrupted상태를 false에서 true로 변경
    boolean isInterrupted()			//쓰레드의 interrupted상태를 반환
    static boolean interrupted()	//현재 쓰레드의 interrupted상태를 반환한 후, false로 변경

    인터넷에서 무언가를 다운로드받을 때 시간이 너무 오래 걸리거나 용량이 클 때 중지시킬 때가 있다. interrupt()는 쓰레드에게 작업을 멈추라고 요청한다. 강제로 종료하지는 못하고 멈추라고 요청할 뿐이다. sleep(), wait(), join()에 의해 일시정지 상태(WAITING)에 있을 때, 해당 쓰레드에 interrupt()를 호출하면 sleep(), wait(), join()에서 InterruptedException이 발생하고 쓰레드는 실행대기 상태(RUNNABLE)로 바뀐다.

    sleep()에 의해 쓰레드가 멈췄을 때 interrupt()를 호출하면 InterruptedException이 발생하고 쓰레드의 interrupt상태는 false로 자동 초기화 된다.

     

    suspend(), resume(), stop()

    suspend(): 쓰레드를 일시정지시킨다. resume()을 호출하면 다시 실행대기상태가 된다.

    resume(): suspend()에 의해 일시정지상태에 있는 쓰레드를 실행대기상태로 만든다.

    stop(): 쓰레드를 즉시 종료시킨다.

     

    suspend()는 sleep()처럼 쓰레드를 멈추게 한다. resume()은 suspend()에 의해 정지된 쓰레드를 다시 실행대기 상태로 만든다. stop()은 호출되는 즉시 쓰레드가 종료된다. suspend()와 stop()은 교착상태를 일으키기 쉬워 Deprecated이다.

     

    yield() - 다른 쓰레드에게 양보한다.

    실행중에 자신에게 주어진 실행시간을 다른 쓰레드에게 양보하고 자신은 실행대기상태가 된다.

     

    join() - 다른 쓰레드의 작업을 기다린다.

    쓰레드 자신이 하던 일을 멈추고 다른 쓰레드가 지정된 시간동안 실행되도록 한다. 지정된 시간이 지나거나 작업이 종료되면 join()을 호출한 쓰레드로 다시 돌아와 실행을 계속 한다. sleep()과 유사한 점이 많지만 join()은 static메서드가 아니므로 현재 쓰레드가 아니라 특정 쓰레드에 대해 동작한다.

    void join()
    void join(long millis)
    void join(long millis, int nanos)
Designed by Tistory.