4. start() & run()
- 실제 Thread가 수행해야 하는 코드는 run() method를 overriding해서 구현한다.
 
- 이 method를 우리가 직접 호출하는 게 아니라 start()라는 non-blocking method를 호출해서 Thread를 동작시킨다.
 
5. Thread의 우선 순위(priority)
- Thread에는 우선 순위라는 게 있다. 기본적으로 5라는 값을 가진다.
 
- 1부터 10까지 범위 내에서 값을 설정해서 사용한다.
 
- main thread는 우선 순위 5를 가진다.
 
- Thread의 우선순위는 자기를 만든 Thread의 우선 순위를 물려 받아서 사용한다.
 
- 당연히 single core에서는 이 우선 순위가 Thread Scheduler에게 영향을 준다.
 
- 하지만 multi core에서는 이 값이 Thread 선택에 영향을 주지 않는다.
 
- 이 우선순위는 Thread가 일단 시작(start)하면 변경할 수 없다.
 
- Thread 객체 상태에서 변경할 수 있다.
 
package test;
public class ThreadPriorityTest {
    public static void main(String[] args) {
        
        
        Thread t1 = new Thread(() -> {
            for(int i=0; i<10; i++) {
                System.out.println(Thread.currentThread().getName());
            }
        });
        t1.setPriority(1);
        Thread t2 = new Thread(() -> {
            for(int i=0; i<10; i++) {
                System.out.println(Thread.currentThread().getName());
            }
        });
        t2.setPriority(10);
        t1.start();
        t2.start();
    }
}
6. Daemon Thread(데몬 쓰레드)
- 우리가 사용하는 일반 Thread와는 다르게 보조적인 역할을 수행하는 Thread.
 
- JVM의 garbage collector, 워드 프로세서의 auto save 기능 같은 것을 생각하자.
 
- 일반 Thread가 모두 종료되면 당연히 Daemon Thread도 존재의 의미가 없어지기 때문에 같이 종료된다.
 
package test;
public class DaemonThreadTest implements Runnable {
    static boolean autuSave = false;
    public static void main(String[] args) {
        Thread t = new Thread(new DaemonThreadTest());
        t.setDaemon(true); 
        t.start(); 
        for (int i=0; i<10; i++) {
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
            }
            System.out.println("i의 값은: "+ i);
            if (i==5) {
                autuSave = true;
            }
        }
    }
    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(3000); 
            } catch (Exception e) {
                System.out.println("오류 발생");
            }
            if (autuSave) { 
                System.out.println("자동 저장");
            }
        }
    }
}
7. Thread의 실행 제어
- 원래 Thread의 상태를 제어하는 쉬운 method가 있었다.
 
- stop(), suspend(), resume() 이다.
 
- 하지만 이런 method는 사용하기 편하지만 코드를 잘못 작성하면 dead-lock에 빠질 확률이 많다.
 
- 따라서 이 method들은 
deprecated 되었다. 
- 대신 실행 제어를 위한 다른 method를 제공한다.
 
7.1 sleep()
- sleep은 지정된 시간 동안 Thread를 멈추는 역할을 수행한다.
 
- sleep()에 의해서 일시 정지된 Thread는 지정된 시간이 다 되거나 혹은 interrupt()라는 method가 호출되면 이때 Exception이 발생하면서 잠에서 깬다.
 
- 따라서 예외 처리(try~catch) 구문이 강제로 사용된다.
 
- 다음과 같은 Thread의 상태 전이가 일어난다.

 
package test;
class Sleep_1 extends Thread {
    @Override
    public void run() {
        try {
            Thread.sleep(2000);
        } catch (Exception e) { }
        
        for (int i=0; i<300; i++) {
            System.out.print("-");
        }
        System.out.println("<<Thread 1 종료>>");
    }
}
class Sleep_2 extends Thread {
    @Override
    public void run() {
        for (int i=0; i<300; i++) {
            System.out.print("|");
        }
        System.out.println("<<Thread 2 종료>>");
    }
}
public class ThreadSleepTest {
    public static void main(String[] args) {
        Thread t1 = new Sleep_1();
        Thread t2 = new Sleep_2();
        t1.start();
        t2.start();
        try {
            
            
            Thread.sleep(2000); 
            
            
        } catch (Exception e) { }
        System.out.println("<< main thread 종료 >>");
    }
}
7.2 interrupt(), interrupted(), isInterrupted()
- 진행 중인 Thread를 작업이 다 끝나기 전에(run method의 수행이 끝나기 전에) 실행을 중지 시켜야 할 때도 있다.
 
- 예제에는 이 작업을 stop() method로 해결했다. 하지만 이제는 이렇게 못한다.
 
- 따라서 interrupt()라는 method를 대신 제공해서 이 작업을 수행할 수 있도록 해준다.
 
- 그런데 한 가지 주의해야 할 점이 있다.
 
- interrupt()는 Thread에게 작업을 중지하라고 요청하는 것이며, stop()처럼 강제로 중지 시키지 않는다.
 
- interrupt() method는 Thread가 내부적으로 가지고 있는 interrupted state를 변경하는 역할을 수행한다.
 
- interrupted(), isInterrupted() 이 2개의 method는 Thread가 내부적으로 가지고 있는 interrupted state 값을 알아오는 것이다.
 
- 만약, 해당 Thread가 
interrupt()가 호출되었으면 true가 리턴 되며, 그렇지 않으면 false가 리턴 된다. 
- 한 가지 조심할 점은 
interrupted() 이 method는 static으로 지정되어 있다.
- 현재 수행 중인 Thread의 interrupted state를 알려준다.
 
- 그 상태를 알려준 다음에 무조건 값을 false로 변경한다.
 
 
- 반면, 
isInterrupted()는 아주 심플하다.
- instance method이다.
 
- 해당 Thread의 interrupted state를 알려주고 아무 일도 하지 않는다.
 
- 즉, 값을 변경하지 않는다.
 
 
- sleep()에 의해서 해당 Thread가 일시 정지 되어 있을 때 해당 Thread에 interrupt()를 호출해서 Interrupt Exception 발생하게 하고 해당 Thread를 Runnable 상태로 변경해준다.
 
package test;
import javax.swing.*;
class MyThread_3 extends Thread {
    @Override
    public void run() {
        int i = 10;
        
        while (i != 0 && !isInterrupted()) { 
            System.out.println(i--);
            for (long k=0; k<25000000000L; k++); 
        }
        System.out.println("카운트가 종료되었습니다.");
    }
}
public class ThreadInterruptTest {
    public static void main(String[] args) {
        Thread t = new MyThread_3();
        t.start();
        
        
        String input = JOptionPane.showInputDialog("값을 입력하세요!");
        System.out.println("입력된 값은 " + input + " 입니다.");
        t.interrupt(); 
    }
}
- 위 코드는 잘 동작하긴 하지만, 사용하면 안 되는 코드가 들어있다.
 
- sleep을 이용해서 해결하여 코드를 다시 써보자.
 
- 한 가지 기억해야 하는 것은 sleep() 도중에 interrupt()가 호출되면 InterruptedException이 발생한다.
 
- 즉, catch문이 수행된다.
 
- 이 다음에 내부적으로 해당 Thread의 interrupted state가 false로 자동 초기화된다.
 
- 그래서 다시 interrupt()를 설정해야 한다.
 
package test;
import javax.swing.*;
class MyThread_3 extends Thread {
    @Override
    public void run() {
        int i = 10;
        
        while (i != 0 && !isInterrupted()) { 
            System.out.println(i--);
            try {
                Thread.sleep(4000); 
                
            } catch (Exception e) { 
                interrupt();
            }
        }
        System.out.println("카운트가 종료되었습니다.");
    }
}
public class ThreadInterruptTest {
    public static void main(String[] args) {
        Thread t = new MyThread_3();
        t.start();
        
        
        
        String input = JOptionPane.showInputDialog("값을 입력하세요!");
        System.out.println("입력된 값은 " + input + " 입니다.");
        t.interrupt(); 
    }
}
7.3 yield()
- yield() method는 Thread 자신에게 주어진 실행 시간을 다음 차례의 Thread에게 양보하는 method이다.
 
- 프로그램의 응답 시간을 줄이기 위해 사용한다.
 
- 일반적으로 응답성을 높이는 목적으로 사용된다.
 
- 만약, yield()가 호출되면 자신은 Runnable 상태로 다시 들어가게 된다.

 
- deprecated 된 suspend(), resume(), stop()을 한번 이용해 보자.
 
package test;
class MyRunnable_4 implements Runnable {
    @Override
    public void run() {
        while (true) {
            System.out.println(Thread.currentThread().getName()); 
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
            }
        }
    }
}
public class ThreadYieldTest {
    public static void main(String[] args) {
        
        MyRunnable_4 r = new MyRunnable_4();
        Thread t1 = new Thread(r, "*"); 
        Thread t2 = new Thread(r, "**"); 
        Thread t3 = new Thread(r, "***"); 
        t1.start();
        t2.start();
        t3.start();
        try {
            Thread.sleep(2000); 
            t1.suspend(); 
            Thread.sleep(2000); 
            t2.suspend(); 
            Thread.sleep(3000); 
            t1.resume();
            Thread.sleep(3000); 
            t1.stop();
            t2.stop();
            Thread.sleep(2000); 
            t3.stop();
        } catch (Exception e) {
        }
    }
}
- 위의 코드를 변수와 로직을 이용해서 똑같이 구현해 보자.
 
package test;
class MyRunnable_4 implements Runnable {
    
    
    volatile boolean suspended = false;
    volatile boolean stoped = false;
    private Thread t;
    public void setThread(Thread t) {
        this.t = t;
    }
    @Override
    public void run() {
        while (!stoped) {
            if (!suspended) {
                System.out.println(Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (Exception e) {
                }
            } else {
                Thread.yield();
            }
        }
    }
    public void suspend() {
        suspended = true;
        
        
        t.interrupt();
    }
    public void resume() {
        suspended = false;
    }
    public void stop() {
        stoped = true;
    }
}
public class ThreadYieldTest {
    public static void main(String[] args) {
        
        MyRunnable_4 r1 = new MyRunnable_4();
        MyRunnable_4 r2 = new MyRunnable_4();
        MyRunnable_4 r3 = new MyRunnable_4();
        Thread t1 = new Thread(r1, "*"); 
        Thread t2 = new Thread(r2, "**"); 
        Thread t3 = new Thread(r3, "***"); 
        r1.setThread(t1);
        r2.setThread(t2);
        r3.setThread(t3);
        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 (Exception e) {
        }
    }
}
7.4 join()
- Thread가 자신이 하던 작업을 잠시 멈추고 다른 Thread를 지정된 시간 혹은 해당 Thread가 일을 다 마칠 때까지 Thread의 실행 시간을 확보하는 method
 
- join()도 sleep()처럼 interrupt()에 의해서 대기 상태에서 벗어날 수 있다. try-cath 구문을 이용해야 한다.
 
- 기억해야 하는 점 중 하나는 sleep()은 static method이다. joint은 특정 Thread를 지칭해야 하기 때문에 static이 아니다.
 
package test;
class MyThread_8 extends Thread {
    @Override
    public void run() {
        for(int i=0; i<100; i++) {
            System.out.println("-");
        }
    }
}
class MyThread_9 extends Thread {
    @Override
    public void run() {
        for(int i=0; i<100; i++) {
            System.out.println("|");
        }
    }
}
public class ThreadJoinTest {
    public static void main(String[] args) {
        MyThread_8 t1 = new MyThread_8();
        MyThread_9 t2 = new MyThread_9();
        t1.start();
        t2.start();
        try {
            t1.join(); 
            t2.join();
        } catch (Exception e) {
        } 
    }
}