[자바의 신] 25장 (ongoing)
25장 - 쓰레드는 개발자라면 알아두는 것이 좋아요
단일 쓰레드(Thread) java라는 명령어 실행 시 결과 출력하고 끝냄 |
➡ | 한 번 실행해 놓고, 계속 기능들 제공하는 프로그램 ex) JSP, Servlet |
프로세스가 시작되는 과정
- 자바 프로그램 사용해
- 뒤에 클래스 이름 붙이고
- 엔터 치면
- 적어도 하나의 JVM 시작됨
- 보통 이렇게 JVM 시작되면 자바 프로세스(Proceses)가 시작됨
- 이 프로세스라는 울타리 안에서 여러 개의 쓰레드가 실행됨
- 위의 순서로 하나의 프로세스 내에 여러 쓰레드가 수행됨
- But 여러 프로세스가 공유하는 하나의 쓰레드가 수행되는 일은 없음
- java 명령어 사용해 클래스 실행시키면
- 자바 프로세스 시작
- main() 메서드 수행되며
- 하나의 쓰레드가 시작됨
- 만약 많은 쓰레드 필요 시, 이 main() 메서드에 쓰레드 생성해주면 됨
- 자바 사용해 웹 제공 시에는 Tomcat 같은 WAS (Web Application Server) 사용
- 이때의 WAS도 main() 메서드에서 생성한 쓰레드들이 수행
- 아무런 쓰레드 생성하지 않아도 JVM 관리하기 위한 여러 쓰레드가 존재함
- ex) 자바의 쓰레기 객체 청소하는 GC 관련 쓰레드
쓰레드가 필요한 이유
- 프로세스가 하나 시작하려면 많은 자원(resource) 필요
- 하나의 작업 동시 수행 시 여러 개의 프로세스 띄워 실행하면 각각 메모리 할당해줘야 함
- JVM은 기본적으로 아무 옵션 없이 실행하면 OS마다 다르나, 적어도 32 ~ 64MB의 물리 메모리 점유함
- But 쓰레드 하나 추가 시 1MB 이내의 메모리 점유함
- ➡ 쓰레드 = 경량 프로세스(lightweight process)
- 요즘은 두 개 이상의 코어가 있는 멀티 코어 시대 ➡ 단일 쓰레드보단 다중 쓰레드로 실행하는 작업이 더 빠른 시간에 결과 제공해줌
Thread
실타래
쓰레드 하나 생성 시 하나의 실 줄기 추가됨
해당 쓰레드 종료 시 그 실이 끊어지는 것
쓰레드 생성 방법
Runnable 인터페이스 | Thread 클래스 |
Runnable 인터페이스를 구현한 클래스 | |
java.lang 패키지에 있으므로 별도의 import 필요 x |
Runnable 인터페이스에 선언된 유일한 메서드
리턴 타입 | 메서드 이름 및 매개 변수 | 설명 |
void | run() | 쓰레드 시작되면 수행되는 메서드 |
package e.thread;
class RunnableSample implements Runnable {
public void run() {
System.out.println("This is RunnableSample's run() method.");
}
}
class ThreadSample extends Thread {
public void run() {
System.out.println("This is ThreadSample's run() method.");
}
}
package e.thread;
public class RunThreads {
public static void main(String[] args) {
RunThreads threads = new RunThreads();
threads.runBasic();
}
public void runBasic() {
RunnableSample runnable = new RunnableSample();
new Thread(runnable).start();
ThreadSample thread = new ThreadSample();
thread.start();
System.out.println("RunThreads.runBasic() method is ended.");
}
}
- run(): 쓰레드가 수행되는 우리가 구현하는 메서드
- start(): 쓰레드 시작하는 메서드
- = Runnable 인터페이스 구현 or Thread 클래스 확장 시 run() 메서드를 시작점으로 작성해야
- But 쓰레드 시작하는 메서드는 run()이 아닌 start()
- start() 메서드 만들지 않아도, 자바가 알아서 run() 메서드 수행함
new Thread(runnable).start();
thread.start();
- Thread 클래스의 생성자에 해당 객체 추가하여 시작해야 함
- 이후 start() 호출 시 쓰레드 시작
쓰레드 클래스가 다른 클래스 확장할 필요 있을 경우
- Runnable 인터페이스 구현
- 인터페이스는 다중 구현해도 문제 x
- 클래스는 자바에서 다중 상속 불가함
쓰레드에서의 순서
쓰레드 결과는 순차적으로(sequential) 실행되지 않음
쓰레드를 start() 통해 시작 = 프로세스가 아닌 하나의 쓰레드를 JVM에 추가해 실행
runBasic() 쓰레드 기동시키는 메서드에서 runnable 객체를 Thread 클래스의 start() 메서드로 시작
이때 시작한 start() 메서드가 끝날 때까지 기다리지 않고, 그 다음 줄에 있는 thread라는 객체의 start() 메서드 실행
package e.thread;
public class RunMultiThreads {
pubilc static void main(String[] args) {
RunMultiThreads sample = new RunMultiThreads();
sample.runMultiThread();
}
public void runMultiThread() {
RunnableSample[] runnable = new RunnableSample[5];
ThreadSample[] thread = new ThreadSample[5];
for(int loop = 0; loop < 5; loop++) {
runnable[loop] = new RunnableSample();
thread[loop] = new ThreadSample();
new Thread(runnable[loop]).start();
thread[loop].start();
}
System.out.println("RunMultiThreads.runMultiThread() method is ended.");
}
}
//결과
//This is RunnableSample's run() method.
//This is RunnableSample's run() method.
//This is RunnableSample's run() method.
//This is ThreadSample's run() method.
//RunMultiThreads.runMultiThread() method is ended.
//This is ThreadSample's run() method.
//This is ThreadSample's run() method.
//This is RunnableSample's run() method.
//This is ThreadSample's run() method.
//This is RunnableSample's run() method.
//This is ThreadSample's run() method.
- 새로 생성한 쓰레드는 run() 메서드 종료 시 끝남
- run() 끝나지 않으면 실행한 애플리케이션은 끝나지 않음
Thread 클래스의 생성자
생성자 | 설명 |
Thread() | 새로운 쓰레드 생성 |
Thread(Runnable target) | 매개 변수로 받은 target 객체의 run() 메서드 수행하는 쓰레드 생성 |
Thread(Runnable target, String name) | 매개 변수로 받은 target 객체의 run() 메서드 수행 후 name이라는 이름 갖는 쓰레드 생성 |
Thread(String name) | name이라는 이름 갖는 쓰레드 생성 |
Thread(ThreadGroup group, Runnable target) | 매개 변수로 받은 group의 쓰레드 그룹에 속하는 target 객체의 run() 메서드 수행하는 쓰레드 생성 |
Thread(ThreadGroup group, Runnable target, String name) | 매개 변수로 받은 group의 쓰레드 그룹에 속하는 target 객체의 run() 메서드 수행 후, name이라는 이름 갖는 쓰레드 생성 |
Thread(ThreadGroup group, Runnable target, String name, long stackSize) | 매개 변수로 받은 group의 쓰레드 그룹에 속하는 target 객체의 run() 메서드 수행 후, name이라는 이름 갖는 쓰레드 생성 해당 쓰레드의 스택 크기는 stackSize 만큼만 가능 |
Thread(ThreadGroup group, Stringn name) | 매개 변수로 받은 group의 쓰레드 그룹에 속하는 name이라는 이름 갖는 쓰레드 생성 |
쓰레드의 이름
- 모든 쓰레드는 이름 있음
- 아무 이름도 지정하지 않으면 그 쓰레드의 이름은 "Thread-n"
- 이때의 n = 쓰레드 생성 순서에 따라 증가
- 쓰레드 이름 지정 시 해당 쓰레드는 별도의 이름 갖게 됨(쓰레드 이름이 겹쳐도 예외나 에러 발생 x)
ThreadGroup
- 어떤 쓰레드 생성 시 ThreadGroup 통해 쓰레드 묶어놓을 수 있음
- 쓰레드의 그룹을 묶으면 ThreadGroup 클래스에서 제공하는 메서드들 통해 각종 정보 얻을 수 있음
stackSize
- 스택의 크기 의미
- 쓰레드에서 얼마나 많은 메서드 호출하는지, 얼마나 많은 쓰레드가 동시에 처리되는지는 JVM이 실행되는 OS 플랫폼에 따라 매우 다름(경우에 따라 이 값이 무시될 수도 있음)
- 여기서의 스택 != Collection의 Stack
- 자바 프로세스 시작 시 실행 데이터 공간(Runtime data area) 구성되는데, 그 중 하나가 스택이라는 공간이며, 쓰레드 생성될 때마다 별도의 스택이 할당됨
Thread 클래스 확장해 구현 시, 생성자 호출하는 방법
package e.thread;
public class NameThread extends Thread {
//쓰레드 이름 지정 방법
public NameThread() {
//Thread(String name) 호출한 효과
//이 경우, 이 쓰레드 객체 여러 개 생성 시 전부 동일한 이름 가지는 문제 발생
super("ThreadName");
}
//위의 동일한 이름 가지는 여러 개의 쓰레드 생성되는 문제 해결법
public NameThread2(String name) {
super(name);
}
public void run() {
}
}
- run(): 쓰레드 실행 시의 진입점
- start(): 쓰레드 시작
쓰레드 시작 시, 값 동적으로 지정 후 전달하는 방법
package e.thread;
public class NameCalcThread extends Thread {
private int calcNumber;
public NameCalcThread(String name, int calcNumber) {
super(name);
this.calcNumber = calcNumber;
}
public void run() {
calcNumber++;
}
}
- deprecated 메서드 : 더 이상 사용 x
- static 메서드 : 객체 생성하지 않아도 사용 가능
- Thread에 있는 static 메서드 : 대부분 해당 쓰레드 위해 존재하는 것이 아니라, JVM에 있는 쓰레드 관리하기 위한 용도로 사용됨 ➡ 여기서의 예외 중 하나가 sleep() 메서드
sleep() 메서드
리턴 타입 | 메서드 이름 및 매개 변수 | 설명 |
static void | sleep(long millis) | 매개 변수로 넘어온 시간(1/1,000초)만큼 대기 |
static void | sleep(long millis, int nanos) | 첫 번째 매개 변수로 넘어온 시간(1/1,000초) + 두 번째 매개 변수로 넘어온 시간(1/1,000,000,000초) 만큼 대기 |
- run() 메서드 끝나지 않으면 애플리케이션은 끝나지 않음(데몬 쓰레드는 예외)
package e.thread;
public class EndlessThread extends Thread {
public void run() {
while(true) { //break 호출 or 예외 발생시키지 않는 한 멈추지 않음
try {
System.out.println(System.currentTimeMillis());
Thread.sleep(1000);
} catch(InterruptedException e) {
e.printStackTrace();
}
}
}
}
Thread.sleep() 사용 시 유의사항
항상 try-catch로 묶어줘야 함
적어도 InterruptedException으로 catch해줘야 함(반드시 해당 에러가 아니더라도, Exception 같은 상위 예외 사용해도 됨)
Why? sleep() 메서드는 InterruptedException 던질 수도 있다고 선언돼있기 때문
package e.thread;
public class RunEndlessThreads {
public static void main(String[] args) {
RunEndlessThreads sample = new RunEndlessThreads();
sample.endless();
}
public void endless() {
EndlessThread thread = new EndlessThread();
thread.start();
}
}
main() 메서드의 수행이 끝나더라도
main() 메서드나 다른 메서드에서 시작한 쓰레드가 종료되지 않으면
해당 자바 프로세스는 끝나지 않음
(데몬 쓰레드는 예외)
Thread 클래스의 주요 메서드
쓰레드의 속성 확인하고 지정하기 위한 메서드
리턴 타입 | 메서드 이름 및 매개 변수 | 설명 |
void | run() | 반드시 구현해야 하는 메서드 |
long | getId() | 쓰레드의 고유 id 리턴 JVM에서 자동으로 생성해줌 |
String | getName() | 쓰레드의 이름 리턴 |
void | setName(String name) | 쓰레드의 이름 지정 |
int | getPriority() | 쓰레드의 우선 순위 확인 |
void | setPriority(int newPriority) | 쓰레드의 우선 순위 지정 |
boolean | isDaemon() | 쓰레드가 데몬인지 확인 |
void | setDaemon(boolean on) | 쓰레드를 데몬으로 설정할지 아닌지를 설정 |
StackTraceElement[] | getStackTrace() | 쓰레드의 스택 정보 확인 |
Thread.State | getState() | 쓰레드의 상태 확인 |
ThreadGroup | getThreadGroup() | 쓰레드의 그룹 확인 |
쓰레드의 우선 순위(Priority)
쓰레드 우선 순위 = 대기하고 있는 상황엥서 더 먼저 수행할 수 있는 순위
대부분 이 값은 기본값 사용을 권장함(자칫하면 장애로 연결될 수 있기에)
package e.thread;
public class RunDaemonThreads {
public static void main(String[] args) {
RunDaemonThreads sample = new RunDaemonThreads();
sample.checkThreadProperty();
}
public void checkThreadProperty() {
ThreadSample thread1 = new ThreadSample();
ThreadSample thread2 = new ThreadSample();
ThreadSample daemonThread = new ThreadSample();
System.out.println("thread1 id=" + thread1.getId());
System.out.println("thread2 id=" + thread2.getId());
System.out.println("thread1 name=" + thread1.getName());
System.out.println("thread2 name=" + thread2.getName());
System.out.println("thread1 priority=" + thread1.getPriority()); //기본값 = 5
daemonThread.setDaemon(true);
System.out.println("thread1 isDaemon=" + thread1.isDaemon());
System.out.println("daemonThread isDaemon=" + daemonThread.isDaemon());
}
}
//결과
//thread1 id=11
//thread2 id=12
//thread1 name=Thread-0
//thread2 name=Thread-1
//thread1 priority=5
//thread1 isDaemon=false
//daemonThread isDaemon=true
쓰레드 우선순위 관련 상수
상수 | 값 및 설명 |
MAX_PRIORITY | 가장 높은 우선 순위 / 값 = 10 |
NORM_PRIORITY | 일반 쓰레드의 우선 순위 / 값 = 5 |
MIN_PRIORITY | 가장 낮은 우선 순위 / 값 = 1 |
우선 순위 정할 때 숫자보다는 위 상수 이용하는 게 better (우선 순위는 되도록 지정하지 않는 게 좋음)
데몬 쓰레드
데몬 쓰레드가 아닌, 사용자 쓰레드는 JVM이 해당 쓰레드가 끝날 때까지 기다림
= 어떤 쓰레드를 데몬으로 지정 시, 그 쓰레드의 수행 여부에 상관 없이 JVM이 끝날 수 있음
단, 해당 쓰레드가 시작하기(start() 메서드가 호출되기) 전에 데몬 쓰레도 지정되어야 함
쓰레드가 시작한 다음에는 데몬으로 지정 불가
package e.thread;
public class DaemonThread extends Thread {
public void run() {
try {
Thread.sleep(Long.MAX_VALUE);
} catch(Exception e) {
e.printStackTrace();
}
}
}
public void runCommonThread() {
DaemonThread thread = new DaemonThread();
thread.setDaemon(true);
thread.start();
}
- 데몬 쓰레드 지정 시, 프로그램이 대기하지 않고 그냥 끝남
- = 데몬 쓰레드는 해당 쓰레드가 종료되지 않아도 다른 실행 중인 일반 쓰레드가 없으면 멈춤
데몬 쓰레도 만든 이유
- 부가적인 작업 수행하는 쓰레드 선언 시 사용하기 위해
- ex) 모니터링하는 쓰레드를 별도로 띄워 모니터링하다, 주요 쓰레드 종료 시 관련된 모니터링 쓰레드가 종료돼야 프로세스가 종료될 수 있음. But 모니터링 쓰레드를 데몬 쓰레드로 만들지 않으면 프로세스 종료 불가하게 됨
synchronized
- 자바의 예약어, 변수명이나 클래스명으로 사용 불가
- 어떤 클래스나 메서드가 쓰레드에 안전하게 하기 위해 사용
- ex) 여러 쓰레드가 한 객체에 선언된 메서드에 접근해 데이터 처리하려 할 때, 동시에 연산을 수행해 값이 꼬이는 경우 발생할 수 있음
- (여기서의 한 객체 = 하나의 클래스에서 생성도니 여러 개의 객체 x, 동일한 하나의 객체)
- 단, 메서드에서 인스턴스 변수 수정하려 할 때만 이런 문제 발생함. 매개 변수나 메서드에서만 사용하는 지역 변수만 다루는 메서드는 synchronized로 선언할 필요 x
synchronized 사용 방법
- 메서드 자체를 synchronized로 선언(synchronized methods)
- 메서드 내의 특정 문장만 synchronized로 감싸기(synchronized statements)
메서드 자체를 synchronized로 선언(synchronized methods)
public synchronized void plus(int value) {
amount += value;
}
- synchronized가 메서드 선언부에 있으면 동일 객체의 이 메서드에 여러 개의 쓰레드가 접근해도 한 순간에는 하나의 쓰레드만 이 메서드를 수행하게 됨
package e.thread.sync;
public class CommonCalculate {
private int amount;
public CommonCalculate() {
amouont = 0;
}
public void plus(int value) {
amount += value;
}
public void minus(int value) {
amount -= value;
}
public int getAmount() {
return amount;
}
}
package e.thread.sync;
public class ModifyAmountThread extends Thread {
private CommonCalculate calc;
private booolean addFlag;
public ModifyAmountThread(CommonCalculate calc, boolean addFlag) {
this.calc = calc;
this.addFlag = addFlag;
}
public void run() {
for(int loop - 0; loop < 10000; loop++) {
if(addFlag) {
calc.plus(1);
} else {
calc.minus(1);
}
}
}
}
package e.thread.sync;
public class RunSync {
public static void main(String[] args) {
RunSync runSync = new RunSync();
runSync.runCommonCalculate();
}
public void runCommonCalculate() {
CommonCalculate calc = new CommonCalculate();
ModifyAmouontThread thread1 = new ModifyAmouontThread(calc, true);
ModifyAmouontThread thread2 = new ModifyAmouontThread(calc, true);
thread1.start();
thread2.start();
try {
thread1.join(); //쓰레드가 종료될 때까지 기다림
thread2.join();
//join() 메서드 수행 이후이므로 모든 쓰레드 종료된 이후의 결과
System.out.println("Final vaue is " + calc.getAmouont());
} catch(InterruptedException e) {
e.printStackTrace();
}
}
}
- 위 소스에 대한 결과
Final value is 19511
- RynSync 클래스의 runCommonCalculate() 메서드 수행된 후 두 개의 쓰레드에서 하나의 객체에 있는 amount라는 int 타입의 값에 1을 만번 더한 결과 출력되어야 하는데, 다른 결과가 나옴
- Why? plus() 메서드는 다른 쓰레드에서 작업하고 있더라도 새로운 쓰레드에서 온 작업도 같이 처리하기에 데이터가 꼬이게 됨
쓰레드 - 0 | 쓰레드 - 1 | ||
현재 연산 | amount의 값 | 현재 연산 | amount의 값 |
amount + value | 1 | 다른 작업 중 | |
더하기 완료 | 1 | amount + value | 1 |
좌측 항에 치환 | 2 | 더하기 완료 | 1 |
다른 작업 수행 | 좌측 항에 치환 | 2 |
- 위처럼 동시에 연산 수행되기에 예상 값인 20,000이라는 값 출력되지 못함
- synchronized 추가해서 문제 해결하기
- 해당 메서드는 동일 객체를 참조하는 다른 쓰레드에서 이 메서드를 변경하려 할 때, 먼저 들어온 쓰레드가 종료될 때까지 기다림
- 수정된 메서드
public synchronized void plus(int value) {
amount += value;
}
public synchronized void minus(int value) {
amount -= value;
}
//결과
//Final is 20000
synchronized 블럭
public void plus(int value) {
synchronized(this) {
amount += value;
}
}
public void minus(int value) {
synchronized(this) {
amount -= value;
}
}
synchronized(this) 이후에 있는 중괄호 내의 연산만 동시에 여러 쓰레드에서 처리하지 않겠다는 의미
소괄호 안의 this 부분에는 잠금 처리 하기 위한 객체 선언
Object lock = new Object();
public void plus(int value) {
synchronized(lock) {
amount += value;
}
}
public void minus(int value) {
synchronized(lock) {
amount -= value;
}
}
synchronized 사용 시, 하나의 객체 사용해 블록 내의 문장을 하나의 쓰레드만 수행하도록 할 수 있음
여기서의 lock이나 this: 문지기 역할, 한 명의 쓰레드만 일할 수 있도록 허용 후 다른 쓰레드에 기회 제공
두 개의 별도의 lock 객체 사용해 효율성 높이기
ex) 클래스에 amount라는 변수 외에 interest라는 변수가 있는데, interest 변수 처리 시 여러 쓰레드에서 접근하면 안 되는 경우 발생할 수 있음. 이럴 때 lock이라는 하나의 잠금용 객체만 사용하면 amount 변수 처리 시 interest 변수 부분도 처리 못하게 됨. 이때 별도의 lock 사용해보자.
//before
private int amount;
private int interest;
public void addInterest(int value) {
interest += value;
}
public void plus(int value) {
amount += value;
}
//after
private int amount;
private int interest;
private Object interestLock = new Object();
private Object amountLock = new Object(0;
public void addInterest(int value) {
synchronized(interestLock) {
interest += value;
}
}
public void plus(int value) {
synchronized(amountLock) {
amount += value;
}
}
synchronized 사용 시 유의점
- 메서드를 synchronized할 때는 같은 객체 참조 시에만 유효함
CommonCalculate calc = new CommonCalculate();
ModifyAmountThread thread1 = new ModifyAmountThread(calc, true);
ModifyAmountThread thread2 = new ModifyAmountThread(calc, true);
CommonCalculate calc1 = new CommonCalculate();
ModifyAmountThread thread1 = new ModifyAmountThread(calc1, true);
CommonCalculate calc2 = new CommonCalculate();
ModifyAmountThread thread2 = new ModifyAmountThread(calc2, true);
- 이처럼 두 개의 쓰레드가 동일한 calc가 아닌 서로 다른 객체 참조 시, synchronized로 선언된 메서드는 같은 객체를 참조하는 것이 아니기에 synchronized를 안 쓰는 것과 동일함
- synchronized는 여러 쓰레드에서 하나의 객체에 있는 인스턴스 변수를 동시 처리 시 발생할 수 있는 문제를 해결하기 위해 필요함
- = 인스턴스 변수가 선언되어 있더라도 변수가 선언된 객체를 다른 쓰레드에서 공유할 일이 없다면 synchronized 사용할 이유 없음
StringBuffer | StringBuilder |
Thread safe | Thread safe x |
하나의 문자열 객체를 여러 쓰레드에서 굥유해야 하는 경우에만 사용 | 여러 쓰레드에서 공유할 일 없을 때 사용 |
쓰레드 통제하는 메서드
리턴 타입 | 메서드 이름 및 매개 변수 | 설명 |
Thread.State | getState() | 쓰레드 상태 확인 |
void | join() | 수행 중인 쓰레드가 중지할 때까지 대기 |
void | join(long millis) | 매개 변수에 지정된 시간만큼(1/1,000초) 대기 |
void | join(long millis, int nanos) | 첫 번째 매개 변수에 지정된 시간(1/1,000초) + 두 번째 매개 변수에 지정된 시간(1/1,000,000,000초)만큼 대기 |
void | interrupt() | 수행 중인 쓰레드에 중지 요청 |
Thread.State
Thread 클래스의 State라는 enum 클래스에 있는 상수 목록
상태 | 의미 |
NEW | 쓰레드 객체는 생성됐으나 아직 시작되진 않은 상태 |
RUNNABLE | 쓰레드가 실행 중인 상태 |
BLOCKED | 쓰레드가 실행 중지 상태이며, 모니터 락(monitor lock)이 풀리길 기다리는 상태 |
WAITING | 쓰레드가 대기 중인 상태 |
TIMED_WAITING | 특정 시간만큼 쓰레드가 대기 중인 상태 |
TERMINATED | 쓰레드가 종료된 상태 |
- 이 클래스는 public static으로 선언됨
- ex) Thread.State.NEW처럼 사용 가능
- 어떤 쓰레드건 NEW ➡ 상태 ➡ TERMINATED 라는 라이프 사이클 가짐
- 여기서 '상태'에 해당하는 것 = NEW, TERMINATED 제외한 모든 것
Thread state diagram
join()
- 매개 변수 없는 join(): 해당 쓰레드가 끝날 때까지 무한대로 대기
thread.join()
- 만약 1분간 대기하고 싶다면
thread.join(60000);
- 더 세부적인 시간대를 지정하고 싶다면
thrad.join(2,3);
//첫 번째 매개 변수 = 2 / 1,000
//밀리초(1 / 1,000초) 단위
//두 번째 매개 변수 = 3 / 1,000,000,000
//나노초(1 / 1,000,000,000초) 단위
//총 대기 시간 = 2 / 1,000 + 3 / 1,000,000,000 = 0.002000003초
interrupt()
- 현재 수행 중인 쓰레드를 InterruptedException 발생시키며 중단시킴
- InterruptedException: sleep()과 join() 메서드에서 발생하는 예외기도 함
- 즉, sleep()과 join() 메서드처럼 대기 상태 만드는 메서드가 호출되면 interrupt() 메서드 호출 가능
- Object 클래스의 wait() 메서드 호출된 상태에서도 이 메서드 사용 가능
stop()
- 안전 상의 이유로 deprecated됨
- interrupt() 사용해 쓰레드 멈춰야 함
package e.thread.support;
public class SleepThread extends Thread {
long sleepTime;
//생성자에서 밀리초 단위 시간을 매개 변수로 받아 인스턴스 변수로 지정
public SleepThread(long sleepTime) {
this.sleepTime = sleepTime;
}
//여기서 위의 인스턴스 변수에 지정된 시간만큼 잠을 잠
public void run() {
try {
System.out.println("Sleeping " + getName());
Thread.sleep(sleepTime);
System.out.println("Stopping " + getName());
} catch(InterruptedException ie) {
ie.printStackTrace();
}
}
}
package e.thread.support;
public class RunSupportThreads {
public static void main(String[] args) {
RunSupportThreads sample = new RunSupportThreads();
sample.checkThreadState1();
}
public void checkThreadState1() {
SleepThread thread = new SleepThread(2000);
try {
System.out.println("thread state = " + thread.getState());
thread.start();
System.out.println("thread state(after start) = " + thread.getState());
thread.sleep(1000);
System.out.println("thread state(after 1 sec) = " + thread.getState());
thread.join();
thread.interrupt();
System.out.println("thread state(after join) = " + thread.getState());
} catch(InterruptedException ie) {
ie.printStackTrace();
}
}
}
/*
결과
thread state = NEW -- 1
thread state(after start) = RUNNABLE -- 2
Sleeping Thread-0
thread state(after 1 sec) = TIMED_WAITING -- 3
Stopping Thread-0
thread state(after join) = TERMINATED -- 4
*/
- 아직 쓰레드 시작 x, Thread.State 중 NEW 상태
- 쓰레드 시작한 상황, 아직 첫 출력문까지 도달하지 않았기에 RUNNABLE 상태
- 2초간 잠자는 모드이기에 TIMED_WAITING
- 쓰레드가 종료되길 join() 메서드에서 기다린 후의 상태이기에 TERMINATED 상태
P.690 checkJoin() ~