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) {
}
}
}