공부기록

쓰레드(1) 본문

Programming/JAVA

쓰레드(1)

코타쿠 2021. 10. 25. 22:15

프로세스의 구현과 실행

public class ThreadEx1 {

    public static void main(String[] args){
        ThreadEx1_1 t1 = new ThreadEx1_1();
        Runnable r = new ThreadEx1_2();
        Thread t2 = new Thread(r);

        t1.start();
        t2.start();
    }

}

class ThreadEx1_1 extends Thread{
    public void run(){
        for(int i=0; i<5; i++){
            System.out.println(getName());
        }
    }
}

class ThreadEx1_2 implements Runnable {
    public void run(){
        for(int i=0; i<5; i++){
            System.out.println(Thread.currentThread().getName());
        }
    }
}

/*  실행결과
Thread-0
Thread-0
Thread-0
Thread-0
Thread-0
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1

Process finished with exit code 0
 */

쓰레드를 구현하는 방법에는 2가지가 있다.

  1. Thread 클래스를 상속

상속하여 run()를 구현해준다.

  1. Runnable 인터페이스를 구현

run()를 구현하고, Thread 클래스의 생성자에 넣어서 쓰레드를 생성

start() 로 시작한다. 이 명령어를 통해 새로운 쓰레드를 위한 수행스택이 생성된다.

Start와 Run

public class ThreadEx2 {

    public static void main(String args[]){
        ThreadEx2_1 t1 = new ThreadEx2_1();
        t1.start();
    }


}

class ThreadEx2_1 extends Thread{
    public void run(){
        throwException();
    }

    public void throwException(){
        try{
            throw new Exception();
        }catch (Exception e){
            e.printStackTrace();
            System.out.println("done");
        }
    }
}

/* ouput

java.lang.Exception
    at ThreadEx2_1.throwException(ThreadEx2.java:18)
    at ThreadEx2_1.run(ThreadEx2.java:13)
done

Process finished with exit code 0

 */

start()를 호출하면 새로운 쓰레드를 생성한다. 이 말인 즉, 새로운 쓰레드를 위해 새로운 수행스택을 생성한다는 말이다. 결과를 보면 쓰레드가 기존의 main 쓰레드와 별개로 자신의 스레드를 생성한 것을 볼 수 있다.

public class ThreadEx3 {
    public static void main(String[] args){
        ThreadEx3_1 t1 = new ThreadEx3_1();
        t1.run();
    }
}

class ThreadEx3_1 extends Thread{
    public void run(){
        throwException();
    }
    public void throwException(){
        try {
            throw new Exception();
        }catch (Exception e){
            e.printStackTrace();
            System.out.println("done");
        }
    }
}

/* output

java.lang.Exception
    at ThreadEx3_1.throwException(ThreadEx3.java:14)
    at ThreadEx3_1.run(ThreadEx3.java:10)
    at ThreadEx3.main(ThreadEx3.java:4)
done

Process finished with exit code 0

 */

이 경우는 그냥 run()을 한 경우이다. 새로운 수행스택이 생기지 않고, main 쓰레드에서 run()이 수행되고 있다.

싱글쓰레드와 멀티쓰레드

public class ThreadEx4 {
    public static void main(String[] args){
        long startTime = System.currentTimeMillis();

        for(int i=0; i<300; i++)
            System.out.printf("%s", new String("-"));
        System.out.print("소요시간1:" + (System.currentTimeMillis() - startTime));

        for(int i=0; i<300; i++)
            System.out.printf("%s", new  String("|"));

        System.out.print("소요시간2:" + (System.currentTimeMillis() - startTime));

    }
}

/* output

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------소요시간1:35------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------소요시간2:42

 */

위 코드는 하나의 쓰레드에서 2개의 작업 ('-'를 출력, '|'를 출력) 을 한 것이고,

public class ThreadEx5 {
    public static long startTime;
    public static void main(String[] args){

        startTime = System.currentTimeMillis();
        ThreadEx5_1 th1 = new ThreadEx5_1();
        th1.run();
        for(int i=0; i<300; i++)
            System.out.printf("%s", new  String("|"));

        System.out.print("소요시간2:" + (System.currentTimeMillis() - startTime));

    }
}

class ThreadEx5_1 extends Thread{
    public void run(){
        for(int i=0; i<300; i++)
            System.out.printf("%s", new String("-"));
        System.out.print("소요시간1:" + (System.currentTimeMillis() - ThreadEx5.startTime));
    }
}

/* output

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------소요시간2:35

 */

위의 코드는 두 개의 작업을 두 개의 쓰레드가 나눠서 한 것이다. 2 개의 쓰레드가 조금 더 작업을 빨리 끝낸 것을 확인할 수 있다.

사실 두 개의 쓰레드가 동시에 수행되면서 하나의 자원을 사용하고자 하면 경쟁조건이 생길 수 밖에 없다.

import javax.swing.*;

public class ThreadEx6 {
    public static void main(String[] args) throws Exception{
        String input = JOptionPane.showInputDialog("아무 값이나 입력하세요.");
        System.out.println("입력하신 값은 " + input + "입니다.");

        for(int i=10; i>0; i--){
            System.out.println(i);
            try{
                Thread.sleep(1000);
            }catch (Exception e){

            }
        }
    }
}

/* output

입력하신 값은 10입니다.
10
9
8
7
6
5
4
3
2
1

Process finished with exit code 0

 */
import javax.swing.*;

public class ThreadEx7 {
    public static void main(String[] args) throws Exception{
        ThreadEx7_1 th1 = new ThreadEx7_1();
        th1.start();
        String input = JOptionPane.showInputDialog("아무 값이나 입력하세요.");
        System.out.println("입력하신 값은 " + input + "입니다.");
    }
}
class ThreadEx7_1 extends Thread{
    public void run(){
        for(int i=10; i>0; i--){
            System.out.println(i);
            try{
                sleep(1000);
            }catch (Exception e){}
        }
    }
}
/* output

10
9
8
7
6
5
입력하신 값은 ㅇㄴ녕입니다.
4
3
2
1

Process finished with exit code 0

 */

위 두 코드를 보면 별개의 쓰레드를 사용하면 별 개의 쓰레드들이 동시에 작동된다는 것을 알 수 있다.

쓰레드의 우선순위

public class ThreadEx8 {
    public static void main(String args[]){
        ThreadEx8_1 th1 = new ThreadEx8_1();
        ThreadEx8_2 th2 = new ThreadEx8_2();

        th2.setPriority(7);

        System.out.println("Priority of th1(-) : " + th1.getPriority());
        System.out.println("Priority of th2(|) : " + th2.getPriority());

        th1.start();
        th2.start();
    }
}

class ThreadEx8_1 extends Thread{
    public void run(){
        for(int i=0; i<300; i++){
            System.out.print("-");
            for(int x=0; x<10000000; x++);
        }
    }
}

class ThreadEx8_2 extends Thread{
    public void run(){
        for(int i=0; i<300; i++){
            System.out.print("|");
            for(int x=0; x<10000000; x++);
        }
    }
}

/*

output

Priority of th1(-) : 5
Priority of th2(|) : 7
-||-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|||||||||||----------------------------------------------|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||

 */

쓰레드간에 우선순위를 줄 수 있다. 우선순위를 줌으로써 더 많은 프로세스 TQ를 가지도록 할 수 있다. 이 것은 th.setPriority(N)으로 할 수 있다. 디폴트 값은 5이다.

쓰레드 그룹

쓰레드는 서로 관련된 쓰레드를 그룹으로 다루기 위한 것이다. 쓰레드 그룹은 보안을 위한 것으로 자신이 속한 쓰레드 그룹, 또는 하위 쓰레드 그룹에 대해서 변경할 수 는 있지만, 다른 쓰레드 그룹은 변경할 수 없도록 한다.

자바 어플리케이션을 실행하면 JVM은 main 그룹, system 그룹을 생성한다. 우리가 생성하는 그룹은 모두 main의 하위 쓰레드 그룹이 되며 디폴트의 경우 자동으로 main 쓰레드 그룹에 포함되게 된다.

중요한 메서드는 다음과 같다.

Threadgroup getThreadgroup() - 쓰레드 자신이 속한 쓰레드 그룹을 반환한다.

void uncaughtException(Thread t, Throwable e) - 쓰레드 그룹의 쓰레드가 처리되지 않은 예외에 의해 실행이 종료되면, JVM에 의해 이 메서드가 자동으로 출력된다.

public class ThreadEx9 {
    public static void main(String[] args){
        ThreadGroup main = Thread.currentThread().getThreadGroup();
        ThreadGroup grp1 = new ThreadGroup("Group1");
        ThreadGroup grp2 = new ThreadGroup("Group2");

        // ThreadGroup(ThreadGroup parent, String name)
        ThreadGroup subGrp1 = new ThreadGroup(grp1, "SubGroup");

        grp1.setMaxPriority(3);

        Runnable r = new Runnable() {
            @Override
            public void run() {
                try{
                    Thread.sleep(1000);
                }catch (InterruptedException e){

                }
            }
        };

        // Thread(ThreadGroup tg, Runnable r, String name)
        new Thread(grp1, r, "th1");
        new Thread(subGrp1, r, "th2");
        new Thread(grp2, r, "th3");

        System.out.println(">>List of ThreadGroup : " + main.getName()
        + ", Active ThreadGroup: " + main.activeGroupCount()
        + ", ActiveThread: " + main.activeCount());
        main.list();

    }
}

/*

output

>>List of ThreadGroup : main, Active ThreadGroup: 3, ActiveThread: 2
java.lang.ThreadGroup[name=main,maxpri=10]
    Thread[main,5,main]
    Thread[Monitor Ctrl-Break,5,main]
    java.lang.ThreadGroup[name=Group1,maxpri=3]
        java.lang.ThreadGroup[name=SubGroup,maxpri=3]
    java.lang.ThreadGroup[name=Group2,maxpri=10]

Process finished with exit code 0

 */

결과를 보면, main 쓰레드 그룹에 main, monitor 쓰레드가 있고, Group1, Group2 쓰레드가 있으며, Group1에는 SubGroup이 있음을 알 수 있다.

데몬 쓰레드

데몬쓰레드는 일반 쓰레드의 작동을 돕는 보조적인 쓰레드다. 종속된 일반 쓰레드가 종료되면 데몬쓰레드도 종료된다. 또한 데몬쓰레드가 생성한 쓰레드는 자동으로 데몬쓰레드가 된다.

주요함수는 다음과 같다.

boolean isDaemon() : 쓰레드가 데몬쓰레드인지 확인하고 맞다면 true 리턴

void setDaemon(boolean on) : 쓰레드를 데몬쓰레드 또는 유저쓰레드로 변경, on이 true이면 데몬쓰레드이다.

public class ThreadEx10 implements Runnable{
    static boolean autosave = false;

    public static void main(String[] args){
        Thread t = new Thread(new ThreadEx10());
        t.setDaemon(true);
        t.start();

        for(int i=1; i<=10; i++){
            try{
                Thread.sleep(1000);
            }catch(Exception e){

            }
            System.out.println(i);
            if(i == 5)
                autosave = true;
        }
        System.out.println("프로그램을 종료합니다.");
    }

    public void run(){
        while(true){
            try{
                Thread.sleep(3*1000);
            }catch (InterruptedException e){

            }
            if(autosave){
                autoSave();
            }
        }
    }

    public void autoSave(){
        System.out.println("작업파일이 자동저장되었습니다.");
    }

}

/*

output

1
2
3
4
5
작업파일이 자동저장되었습니다.
6
7
8
작업파일이 자동저장되었습니다.
9
10
프로그램을 종료합니다.

Process finished with exit code 0

 */

코드의 결과를 보면 데몬쓰레드는 무한루프이지만, 메인쓰레드가 끝나자 자신도 멈추게 된다.

참고로, setDaemon 메서드는 해당 쓰레드 시작 전에 해야 예외가 안난다.

import java.util.Iterator;
import java.util.Map;

public class ThreadEx11 {
    public static void main(String[] args) throws Exception{
        ThreadEx11_1 t1 = new ThreadEx11_1("Thread1");
        ThreadEx11_2 t2 = new ThreadEx11_2("Thread2");
        t1.start();
        t2.start();
    }
}

class ThreadEx11_1 extends Thread{
    ThreadEx11_1(String name){
        super(name);
    }
    public void run(){
        try{
            sleep(5*1000);
        }catch(InterruptedException e){}
    }
}

class ThreadEx11_2 extends Thread{
    ThreadEx11_2(String name){
        super(name);
    }
    public void run(){
        Map map = getAllStackTraces();
        Iterator it = map.keySet().iterator();

        int x=0;
        while(it.hasNext()){
            Object obj = it.next();
            Thread t = (Thread)obj;
            StackTraceElement[] ste = (StackTraceElement[]) (map.get(obj));

            System.out.println("[" + ++x + "] name : " + t.getName()
            + ", group : " + t.getThreadGroup().getName()
            + ", daemon : " + t.isDaemon());
            for(int i=0; i<ste.length; i++){
                System.out.println(ste[i]);
            }
        }
        System.out.println();
    }
}

/*

output

"C:\Program Files\Java\jdk1.8.0_301\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2021.2.1\lib\idea_rt.jar=64942:C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2021.2.1\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_301\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\rt.jar;C:\Users\Yoon Yeol\IdeaProjects\CH13\out\production\CH13" ThreadEx11
[1] name : Thread1, group : main, daemon : false
java.lang.Thread.sleep(Native Method)
ThreadEx11_1.run(ThreadEx11.java:19)
[2] name : Monitor Ctrl-Break, group : main, daemon : true
java.net.SocksSocketImpl.connect(SocksSocketImpl.java:377)
java.net.Socket.connect(Socket.java:606)
java.net.Socket.connect(Socket.java:555)
java.net.Socket.<init>(Socket.java:451)
java.net.Socket.<init>(Socket.java:228)
com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:44)
[3] name : Reference Handler, group : system, daemon : true
java.lang.Object.wait(Native Method)
java.lang.Object.wait(Object.java:502)
java.lang.ref.Reference.tryHandlePending(Reference.java:191)
java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
[4] name : Thread2, group : main, daemon : false
java.lang.Thread.dumpThreads(Native Method)
java.lang.Thread.getAllStackTraces(Thread.java:1610)
ThreadEx11_2.run(ThreadEx11.java:29)
[5] name : Signal Dispatcher, group : system, daemon : true
[6] name : DestroyJavaVM, group : main, daemon : false
[7] name : Attach Listener, group : system, daemon : true
[8] name : Finalizer, group : system, daemon : true
java.lang.Object.wait(Native Method)
java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)


Process finished with exit code 0

 */

위 코드의 결과를 통해 실제로 어떤 쓰레드가 데몬 쓰레드인지 알 수 있게 된다.

쓰레드의 실행제어

쓰레드 프로그래밍을 하면서 동기화도 하게 된다. 쓰레드 동기화를 하게 되면서 어떤 쓰레드는 기다리고, 어떤 쓰레드는 실행되는 등의 상태를 가지게 된다.

쓰레드의 생태를 변화시키는 메서드는 아래와 같다.

  • static void sleep()

지정된 시간 동안 쓰레드를 일시정지 시킨다. 지정된 시간이 끝나면 자동으로 실행 대기가 된다.

  • void join()

지정된 시간동안 쓰레드가 실행되고, 지정된 시간이 지나거나 작업이 종료되면 해당 메서드를 호출한 쓰레드로 돌아와 실행을 재개한다.

  • void interrupt()

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

  • void stop()

쓰레드를 즉시 종료시킨다.

  • void suspend()

쓰레드를 일시정지시킨다. resume()을 호출하여 재개한다.

  • void resume()

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

  • static void yield()

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

쓰레드의 상태는 아래와 같다.

  • NEW

쓰레드가 생성되고 아직 start() 호출되지 않음

  • RUNNABLE

실행 중 or 실행 가능한 상태

  • BLOCKED

동기화 블럭에 의해서 일시정지된 상태 (lock이 풀리기를 기다리는 중)

  • WAITING, TIMED_WAITING

쓰레드의 작업이 종료되지 않았으나, 실행가능하지 않은 일시정지상태. TIMED_WAITING은 일시정지시간이 지정된 경우를 의미한다.

  • TERMINATED

쓰레드의 작업이 종료된 상태

쓰레드의 상태변화는 다음과 같다.

  1. 쓰레드가 생성 (NEW)
  2. start()에 의해 실행대기큐에 들어감 (RUNNABLE)
  3. 자신의 순서가 되어 실행상태가 됨
  4. 실행이 끝나고 stop()에 의해 소멸 (TERMINATED)
  5. suspend() , sleep(), wait(), join(), I/O block에 의해 일시정지 상태가 됨 (WAITING, BLOCKED)
  6. time-out, resume(), notify(), interrupt() 에 의해 실행대기 상태가 됨 (RUNNABLE)

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

주요 함수는 다음과 같다.

static void sleep(long millis)

static void sleep(long millis, int nanos)

sleep()은 지정된 시간동안 스레드를 멈추게 한다.

sleep은 static이다. 따라서 함수를 호출한 쓰레드를 멈추게 한다.

public class ThreadEx12 {
    public static void main(String[] args){
        ThreadEx12_1 th1 = new ThreadEx12_1();
        ThreadEx12_2 th2 = new ThreadEx12_2();
        th1.start();
        th2.start();
        try{
            th1.sleep(2000);
            //Thread.sleep(3000);
        }catch (InterruptedException e){}
        System.out.print("<<main 종료>>");
    }

}

class ThreadEx12_1 extends Thread{
    public void run(){
//        try {
//            Thread.sleep(1000);
//        }catch(InterruptedException e){
//
//        }
        for(int i=0; i<300; i++)
            System.out.print("-");
        System.out.print("<<th1 종료>>");
    }
}

class ThreadEx12_2 extends Thread{
    public void run(){
//        try {
//            Thread.sleep(2000);
//        }catch (InterruptedException e){
//
//        }
        for(int i=0; i<300; i++)
            System.out.print("|");
        System.out.print("<<th2 종료>>");
    }
}

/* output

------------------------------------------------------------------------------------------------------------------------------||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||-||||||||||-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||<<th1 종료>><<th2 종료>><<main 종료>>

 */

위 코드를 보면 main에서 th1.sleep()을 호출하였지만 main이 잠들게 되었다.

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

void interrupt() : 쓰레드의 interrupted 상태를 false에서 true로 변경

boolean isInterrupted() : 쓰레드의 interrupted상태를 반환

static boolean interrupted() : 현재 쓰레드의 interrupted 상태를 반환 후, false로 변경

interrupted()interrupt()가 호출되었다면 true를 그렇지 않다면 false를 리턴한다.

interrupt() 함수는 쓰레드를 멈추고, interrupted 함수는 자신이 다른 쓰레드가 자신을 멈추게 했는지 체크한다.

쓰레드가 WAITING 상태일 때, 해당 쓰레드에 대해 interrupt()를 호출하면 sleep(), wait(), join()에서 Interrupted Exception이 발생하여 실행가능상태 (RUNNABLE) 가 된다. 즉 멈춰있던 쓰레드가 일어나게 된다.

import javax.swing.*;

public class ThreadEx13 {
    public static void main(String[] args){
        ThreadEx13_1 th1 = new ThreadEx13_1();
        th1.start();
        String input = JOptionPane.showInputDialog("아무 값이나 입력하세요.");
        System.out.println("입력하신 값은 " + input + "입니다. ");
        th1.interrupt();
        System.out.println("isInterrupted() : " + th1.isInterrupted());
    }
}

class ThreadEx13_1 extends Thread{
    public void run(){
        int i=10;
        while(i!=0 && !isInterrupted()){
            System.out.println(i--);
            for(long x=0; x<2500000000L;x++);
        }
        System.out.println("카운트가 종료되었습니다.");
    }
}

/* output

10
9
8
7
입력하신 값은 asdf입니다.
isInterrupted() : true
카운트가 종료되었습니다.

 */

위의 예제는 카운트를 세던 쓰레드가 인터럽트 당해 종료되는 모습이다.

import javax.swing.*;

public class ThreadEx14 {
    public static void main(String[] args){
        ThreadEx14_1 th1 = new ThreadEx14_1();
        th1.start();

        String input = JOptionPane.showInputDialog("아무 값이나 입력하세요.");
        System.out.println("입력하신 값은 " + input + "입니다. ");
        th1.interrupt();
        System.out.println("isInterrupted():" + th1.isInterrupted());
    }
}

class ThreadEx14_1 extends Thread{
    public void run(){
        int i=10;
        while(i!=0 && !isInterrupted()){
            System.out.println(i--);
            try{
                Thread.sleep(1000);
            }catch(InterruptedException e){
                e.printStackTrace();
                Thread.currentThread().interrupt(); //output2
                System.out.println(Thread.currentThread().isInterrupted());
            }
        }
        System.out.println("카운트가 종료되었습니다.");
    }
}

/* output

10
9
입력하신 값은 asdf입니다.
isInterrupted():false
false
8
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at ThreadEx14_1.run(ThreadEx14.java:21)
7
6
5
4
3
2
1
카운트가 종료되었습니다.

Process finished with exit code 0

 */

/* output2

10
9
8
입력하신 값은 asdf입니다.
isInterrupted():false
true
카운트가 종료되었습니다.
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at ThreadEx14_1.run(ThreadEx14.java:21)

Process finished with exit code 0


 */

위의 예제에서 output1은 catch에 아무것도 없는 상태이다. 이 때는 쓰레드가 sleep() 상태로 들어가서 interrupt 당해도 그냥 깨어날 뿐이기 때문에 카운터가 종료되지 않는다.

또한 isInterrupted() 의 상태가 false이다. 쓰레드가 sleep 일 때, interrupt() 를 호출하면 예외와 함께 interrupt 상태가 false가 된다.

catch 구문 안에다가 interrupt를 넣으면 결국 쓰레드가 종료된다.

suspend(), resume(), stop()

suspend()는 쓰레드를 멈추게 하고, resume()suspend()된 쓰레드를 재실행한다. stop()를 호출된 쓰레드를 종료시킨다.

suspend()stop()은 교착상태를 발생시킬 수 있어 deprecated된 상태이다.


class ThreadEx15 {
    public static void main(String[] args){
        RunImplEx15 r = new RunImplEx15();
        Thread th1 = new Thread(r, "*");
        Thread th2 = new Thread(r, "**");
        Thread th3 = new Thread(r, "***");
        th1.start();
        th2.start();
        th3.start();
        try{
            Thread.sleep(2000);
            th1.suspend();
            Thread.sleep(2000);
            th2.suspend();
            Thread.sleep(3000);
            th1.resume();
            Thread.sleep(3000);
            th1.stop();
            th2.stop();
            Thread.sleep(2000);
            th3.stop();
        }catch(InterruptedException e){

        }
    }
}

class RunImplEx15 implements Runnable{

    public void run(){
        while(true){
            System.out.println(Thread.currentThread().getName());
            try{
                Thread.sleep(1000);
            }catch(InterruptedException e){}
        }
    }

}

/* output

*
**
***
**
***
*
**
***
**
***
***
***
***
*
***
*
***
*
***
*
***
***

Process finished with exit code 0

 */

위의 코드에서, 각 쓰레드들은 동일한 Runnable객체를 사용하고 있다. 문제는, 만약 이 객체에 어떤 자원이 있고 그 것을 다른 쓰레드들이 공유해서 쓰고 있다면, 교착상태가 발생할 수 있다는 것이다.

그렇기 때문에 다음과 같이 Runnable 객체를 나눠주는 것이 올바르다.

public class ThreadEx16 {
    public static void main(String[] args){
        RunImplEx16 r1 = new RunImplEx16();
        RunImplEx16 r2 = new RunImplEx16();
        RunImplEx16 r3 = new RunImplEx16();
        Thread t1 = new Thread(r1, "*");
        Thread t2 = new Thread(r2, "**");
        Thread t3 = new Thread(r3, "***");
        t1.start();
        t2.start();
        t3.start();
        try{
            Thread.sleep(2000);
            r1.suspend();
            Thread.sleep(2000);
            r2.suspend();
            Thread.sleep(3000);
            r1.resume();
            Thread.sleep(3000);
            r1.stop();
            r2.stop();
            Thread.sleep(2000);
            r3.stop();
        }catch (InterruptedException e){

        }

    }

}

class RunImplEx16 implements Runnable{
    volatile boolean suspended = false;
    volatile boolean stopped = false;

    public void run(){
        while(!stopped){
            if(!suspended){
                System.out.println(Thread.currentThread().getName());
                try{
                    Thread.sleep(1000);
                }catch (InterruptedException e){

                }
            }
        }
        System.out.println(Thread.currentThread().getName() + " - stopped");
    }
    public void suspend(){
        suspended = true;
    }
    public void resume(){
        suspended = false;
    }
    public void stop(){
        stopped = true;
    }

}

/* output

*
**
***
**
***
*
**
***
***
**
***
***
***
*
***
*
***
*
***
** - stopped
* - stopped
***
***
*** - stopped

Process finished with exit code 0

 */

사실 이 예제는 stop(), suspend()를 안 쓰면서 그 상태를 각 쓰레드가 가질 수 있도록 객체를 따로따로 만들어 준 예제이다.

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

yield()는 쓰레드 자신에게 주어진 시간을 양보하여 다른 쓰레드가 사용하도록 하는 것이다.

yield()interrupt()를 적절히 사용하면 효율적인 프로그램을 작성할 수 있다.

public class ThreadEx18 {

    public static void main(String args[]){
        ThreadEx18_1 th1 = new ThreadEx18_1("*");
        ThreadEx18_1 th2 = new ThreadEx18_1("**");
        ThreadEx18_1 th3 = new ThreadEx18_1("***");
        th1.start();
        th2.start();
        th3.start();
        try{
            Thread.sleep(2000);
            th1.suspend();
            Thread.sleep(2000);
            th2.suspend();
            Thread.sleep(3000);
            th1.resume();
            Thread.sleep(3000);
            th1.stop();
            th2.stop();
            Thread.sleep(2000);
            th3.stop();
        }catch(InterruptedException e){

        }
    }
}

class ThreadEx18_1 implements Runnable{
    boolean suspended = false;
    boolean stopped = false;

    Thread th;

    ThreadEx18_1(String name){
        th = new Thread(this, name);
    }
    public void run(){
        String name = th.getName();
        while(!stopped){
            if(!suspended) {
                System.out.println(name);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    System.out.println(name + " - interrupted");
                }
            }else{
                Thread.yield();
            }
        }
    }

    public void suspend(){
        suspended = true;
        th.interrupt();
        System.out.println(th.getName() + " - interrupt() by suspend()");
    }

    public void stop(){
        stopped = true;
        th.interrupt();
        System.out.println(th.getName() + " - interrupt() by stop()");
    }

    public void resume(){
        suspended = false;
    }

    public void start(){
        th.start();
    }

}

위의 코드에서 쓰레드가 suspend 상태라면 루프를 돌지 않고 yield() 하여 WAITING 상태가 된다.

또한 suspend()stop()interrupt()를 넣어 응답성을 개선했는데 이것을 넣음으로써 sleep인 상태에서 suspend, stop 명령이 들어오면 잠에서 깨어 suspend, stop 상태로 전이할 수 있도록 개선하였다.

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

join()은 지정된 시간동안 자신의 작업을 멈추고, 다른 쓰레드의 작업을 하도록 해준다.

join()sleep()처럼 interrupt()에 의해 대기상태에서 벗어날 수 있다. 차이점은 join()은 static 메서드가 아니라는 점이다.

public class ThreadEx19 {
    static long startTime = 0;

    public static void main(String args[]){
        ThreadEx19_1 th1 = new ThreadEx19_1();
        ThreadEx19_2 th2 = new ThreadEx19_2();
        th1.start();
        th2.start();
        startTime = System.currentTimeMillis();
        try{
            th1.join();
            th2.join();
        }catch (InterruptedException e){

        }
        System.out.println("소요시간 : " + (System.currentTimeMillis() - ThreadEx19.startTime));
    }
}

class ThreadEx19_1 extends Thread{
    public void run(){
        for(int i=0; i<300; i++){
            System.out.print(new String("-"));
        }
    }
}

class ThreadEx19_2 extends Thread{
    public void run(){
        for(int i=0; i<300; i++){
            System.out.print(new String("|"));
        }
    }
}

/* output

--------||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||---------------------------------------------------------||||||||||||---------------------------------------------------------||-------------||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||---------------------------------------------------------------------------------------------------------------------------------------------------------------------소요시간 : 6

 */

다음 코드에서, main()은 쓰레드들이 모든 출력을 끝낼 때 까지 기다리고 프로그램을 종료하게 된다.

public class ThreadEx20 {
    public static void main(String[] args){
        ThreadEx20_1 gc = new ThreadEx20_1();
        gc.setDaemon(true);
        gc.start();

        int requireMemory = 0;

        for(int i=0; i<20; i++){
            requireMemory = (int)(Math.random()*10)*20;
            if(gc.freeMemory() < requireMemory
                || gc.freeMemory() < gc.totalMemory()*0.4){
                gc.interrupt();
                try{
                    // output2
                    gc.join(100);
                }catch (InterruptedException e){

                }
            }
            gc.usedMemory += requireMemory;
            System.out.println("usedMemory:"+gc.usedMemory);
        }

    }
}

class ThreadEx20_1 extends Thread{
    final static int MAX_MEMORY = 1000;
    int usedMemory = 0;

    public void run(){
        while(true){
            try{
                Thread.sleep(10*1000);
            }catch (InterruptedException e){
                System.out.println("Awaken by interrupt().");
            }
            gc();
            System.out.println("Garbage Collected. Free Memory :" + freeMemory());
        }

    }

    public void gc(){
        usedMemory -= 300;
        if(usedMemory < 0) usedMemory = 0;
    }

    public int totalMemory(){
        return MAX_MEMORY;
    }

    public int freeMemory(){
        return MAX_MEMORY - usedMemory;
    }

}

/* output

usedMemory:140
usedMemory:220
usedMemory:260
usedMemory:300
usedMemory:320
usedMemory:500
usedMemory:560
usedMemory:700
usedMemory:780
usedMemory:920
Awaken by interrupt().
usedMemory:1080
Garbage Collected. Free Memory :220
usedMemory:880
Awaken by interrupt().
usedMemory:960
Garbage Collected. Free Memory :340
usedMemory:760
Awaken by interrupt().
usedMemory:800
Garbage Collected. Free Memory :500
usedMemory:500
Awaken by interrupt().
usedMemory:540
Garbage Collected. Free Memory :760
usedMemory:240
usedMemory:240
usedMemory:340

Process finished with exit code 0

 */

/* output 2

usedMemory:120
usedMemory:240
usedMemory:420
usedMemory:580
usedMemory:720
Awaken by interrupt().
Garbage Collected. Free Memory :580
usedMemory:500
usedMemory:640
Awaken by interrupt().
Garbage Collected. Free Memory :660
usedMemory:380
usedMemory:440
usedMemory:480
usedMemory:500
usedMemory:540
usedMemory:680
Awaken by interrupt().
Garbage Collected. Free Memory :620
usedMemory:380
usedMemory:380
usedMemory:460
usedMemory:560
usedMemory:680
Awaken by interrupt().
Garbage Collected. Free Memory :620
usedMemory:500
usedMemory:520

Process finished with exit code 0

 */

output1에서 gc를 호출했지만, gc와 main이 함께 수행되어 1000이 넘고만다.

output2에서는 gc를 join을 써서 기다리기에 gc가 수행되는 동안은 main이 수행되지 않아 임계치를 넘지 않을 수 있었다.