프로세스(process)와 쓰레드(thread)
프로세스
- 실행 중인 프로그램이다.
- OS로부터 실행에 필요한 자원을 할당받아 프로세스가 된다.
- 프로그램을 수행하는 데 필요한 자원(데이터 + 메모리) 와 쓰레드로 구성되어 있다.
쓰레드
- 프로세스의 자원을 이용해서 실제로 작업을 수행하는 단위이다.
- 모든 프로세스에는 최소한 하나 이상의 쓰레드가 존재한다.
- 둘 이상의 쓰레드를 가진 프로세스를 ‘멀티쓰레드 프로세스’라고 한다.
멀티쓰레딩의 장단점
멀티쓰레딩: 하나의 프로세스 내에서 여러 쓰레드가 동시에 작업을 수행하는 것이다.
멀티쓰레딩의 장점
- CPU의 사용률을 향상시킨다.
- 자원을 보다 효율적으로 사용할 수 있다.
- 사용자에 대한 응답성이 향상된다.
- 작업이 분리되어 코드가 간결해진다.
멀티쓰레딩의 단점
- 여러 쓰레드가 같은 프로세스 내에서 자원을 공유하면서 작업을 하기 때문에 발생할 수 있는 동기화(synchronizaion), 교착상태(deadlock)와 같은 문제를 고려해야 한다.
쓰레드의 구현과 실행
Thread클래스 상속
class MyThread extends Thread{
public void run() { /* 작업내용 */ } //Thread클래스의 run()을 오버라이딩
}
Runnable인터페이스
Thread클래스 상속 방법은 다른 클래스를 상속받을 수 없기 때문에 Runnable인터페이스를 구현하는 방법이 일반적이다.
class MyThread implements Runnable{
public void run() { /* 작업내용 */ } // Runnable 인터페이스의 run()을 구현
}
쓰레드를 구현한다는 것은, 위의 두 방법 중 어떤 것을 선택하든지, 그저 쓰레드를 통해 작업하고자 하는 내용으로 run()의 몸통{}을 채우는 것일 뿐이다.
쓰레드의 구현과 실행 예제
class Ex13_1 {
public static void main(String args[]) {
ThreadEx1_1 t1 = new ThreadEx1_1();
/*
Runnable 인터페이스를 구현한 경우,
Runnable 인터페이스를 구현한 클래스의 인스턴스를 생성한 다음 (new ThreadEx1_2()),
이 인스턴스를 Thread클래스의 생성자의 매개변수로 제공해야 한다. (new Thread(r))
*/
Runnable r = new ThreadEx1_2();
Thread t2 = new Thread(r); // 생성자 Thread(Runnable target)
t1.start(); //start()를 호출해야만 쓰레드가 실행된다.
t2.start();
}
}
class ThreadEx1_1 extends Thread {
public void run() {
for(int i=0; i < 5; i++) {
System.out.println(getName()); // 조상인 Thread의 getName()을 호출
}
}
}
class ThreadEx1_2 implements Runnable {
public void run() {
for(int i=0; i < 5; i++) {
// Thread.currentThread() - 현재 실행중인 Thread를 반환한다.
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
*/
쓰레드의 실행 - start( )
쓰레드를 생성했다고 해서 자동으로 실행되는 것은 아니다.
start()를 호출해야만 쓰레드가 실행된다.
ThreadEx1_1 t1 = new ThreadEx1_1(); // 쓰레드 t1을 생성한다.
t1.start(); // 쓰레드 t1을 실행시킨다.
start()가 호출되었다고 해서 바로 실행되는 것이 아니라, 일단 실행대기 상태에 있다가 자신의 차례가 되어야 실행된다. 쓰레드의 실행순서는 OS의 스케쥴러가 작성한 스케쥴에 의해 결정된다.
한 번 실행이 종료된 쓰레드는 다시 실행할 수 없다. 즉, 하나의 쓰레드에 대해 start()가 한 번만 호출될 수 있다. 만일 쓰레드의 작업을 한 번 더 수행해야 한다면, 새로운 쓰레드를 생성한 다음에 start()를 호출해야 한다.
ThreadEx1_1 t1 = new ThreadEx1_1();
t1.start();
...
//하나의 쓰레드에 두 개 이상 start() 호출하면 예외가 발생한다.
// t1.start(); // IllegalThreadStateException 예외 발생.
//다시 start()를 하려면 쓰레드 새로 생성해야 한다.
t1 = new ThreadEx1_1();
t1.start();
start( )와 run( )
main메서드에서 run()을 호출하는 것은 생성된 쓰레드를 실행시키는 것이 아니라 단순히 클래스에 선언된 메서드를 호출하는 것일 뿐이다.

start()는 새로운 쓰레드가 작업을 실행하는데 필요한 호출스택(call stack)을 생성한 다음에 run()을 호출해서, 생성된 호출스택에 run()이 첫 번째로 올라가게 한다.
모든 쓰레드는 독립적인 작업을 수행하기 위해 자신만의 호출스택을 필요로 하기 때문에, 새로운 쓰레드를 생성하고 실행시킬 떄마다 새로운 호출스택이 생성되고 쓰레드가 종료되면 작업에 사용된 호출스택은 소멸된다.

main쓰레드
main메서드의 작업을 수행하는 것도 쓰레드이며, 이를 main쓰레드라고 한다.
프로그램을 실행하면 기본적으로 하나의 쓰레드를 생성하고, 그 쓰레드가 main메서드를 호출해서 작업이 수행되도록 하는 것이다.
싱글쓰레드와 멀티쓰레드

하나의 쓰레드(main)로 두 개의 작업을 수행한 시간보다 두 개의 쓰레드(th1과 th2)로 작업한 시간이 더 오래 걸린다. 그 이유는 쓰레드간의 작업 전환(context switching)에 시간이 걸리기 때문이다. 작업 전환할 때 현재 진행 중인 작업의 상태에 관한 정보를 저장해야하므로 더 많은 시간이 소요된다.
싱글 코어에서 단순히 CPU만을 사용하는 계산작업은 싱글쓰레드가 더 효율적이다. 두 쓰레드가 서로 다른 자원을 사용하는 작업의 경우 멀티쓰레드가 더 효율적이다.
쓰레드의 I/O블락킹(blocking)
두 쓰레드가 서로 다른 자원을 사용하는 작업의 경우 싱글쓰레드 프로세스보다 멀티 쓰레드 프로세스가 더 효율적이다. 예를 들면, 사용자로부터 데이터를 입력받는 작업, 네트워크로 파일을 주고받는 작업, 프린터로 파일을 출력하는 작업과 같이 외부기기와의 입출력을 필요로 하는 경우가 이에 해당된다.


쓰레드가 입출력(I/O)처리를 위해 기다리는 것을 I/O블락킹이라고 한다.
쓰레드의 우선순위
작업의 중요도에 따라 쓰레드의 우선순의를 다르게 하여 특정 쓰레드가 더 많은 작업시간을 갖게 할 수 있다.
시작적인 부분이나 사용자에게 빠르게 반응해야하는 작업을 하는 쓰레드의 우선순위는 다른 작업을 수행하는 쓰레드에 비해 높아야 한다.
쓰레드의 우선순위 지정하기
- void setPriority(int newPriority) : 쓰레드의 우선순위를 지정한 값으로 변경한다.
- int getPriority(): 쓰레드의 우선순위 반환
- public static final int MAX_PRIORITY = 10 : 최대 우선순위
- public static final int MIN_PRIORITY = 1 : 최소 우선순위
- public static final int NORM_PRIORITY = 5 : 보통 우선순위
쓰레드를 실행하기 전에만 우선순위를 변경할 수 있다.
쓰레드 그룹(thread group)
쓰레드 그룹은 서로 관련된 쓰레드를 그룹으로 다루기 위한 것으로, 폴더를 생성해서 관련된 파일들을 함께 넣어서 관리하는 것처럼 쓰레드 그룹을 생성해서 쓰레드를 그룹으로 묶어서 관리할 수 있다.
폴더 안에 폴더를 생성할 수 있듯이 쓰레드 그룹에 다른 쓰레드 그룹을 포함 시킬 수 있다.
쓰레드를 쓰레드 그룹에 포함시키려면 Thread의 생성자를 이용하면 된다.
Thread(ThreadGroup group, String name)
모든 쓰레드는 반드시 쓰레드 그룹에 포함되어 있어야 하기 때문에, 쓰레드 그룹을 지정하는 생성자를 사용하지 않은 쓰레드는 기본적으로 자신을 생성한 쓰레드와 같은 쓰레드 그룹에 속하게 된다.
자바 어플리케이션 실행 시, JVM은 main과 system이라는 쓰레드 그룹을 만든다. 우리가 생성하는 모든 쓰레드 그룹은 main 쓰레드 그룹의 하위 쓰레드 그룹이 되며, 쓰레드 그룹을 지정하지 않고 생성한 쓰레드는 자동적으로 main쓰레드 그룹에 속하게 된다.
데몬 쓰레드(daemon thread)
데몬 쓰레드는 다른 일반 쓰레드(데몬 쓰레드가 아닌 쓰레드)의 작업을 돕는 보조적인 역할을 수행하는 쓰레드이다.
일반 쓰레드가 모두 종료되면 데몬 쓰레드는 강제적으로 자동 종료된다.
예를 들면, 가비지 컬렉터, 워드프로세서의 자동저장, 화면자동갱신 등이 있다.
데몬 쓰레드 생성 방법은 쓰레드를 생성한 다음 실행하기 전에 setDaemon(true)를 호출하면 된다.
데몬 쓰레드가 생성한 쓰레드는 자동적으로 데몬 쓰레드가 된다.
데몬 쓰레드(daemon thread) 예제
class Ex13_7 implements Runnable {
static boolean autoSave = false;
public static void main(String[] args) {
Thread t = new Thread(new Ex13_7());
t.setDaemon(true); // 이 부분이 없으면 종료되지 않는다.
t.start();
for(int i=1; i <= 10; i++) {
try{
Thread.sleep(1000);
} catch(InterruptedException e) {}
System.out.println(i);
if(i==5) autoSave = true;
}
System.out.println("프로그램을 종료합니다.");
}
public void run() {
while(true) {
try {
Thread.sleep(3 * 1000); // 3초마다
} catch(InterruptedException e) {}
// autoSave의 값이 true이면 autoSave()를 호출한다.
if(autoSave) autoSave();
}
}
public void autoSave() {
System.out.println("작업파일이 자동저장되었습니다.");
}
}
/* 출력 결과
1
2
3
4
5
6
작업파일이 자동저장되었습니다.
7
8
작업파일이 자동저장되었습니다.
9
10
프로그램을 종료합니다.
*/
쓰레드의 상태
쓰레드의 상태
- NEW : 쓰레드가 생성되고 아직 start()가 호출되지 않은 상태
- RUNNABLE: 실행 중 또는 실행 가능한 상태
- BLOCKED: 동기화 블럭에 의해서 일시정지된 상태(lock이 풀릴 때까지 기다리는 상태)
- WAITING, TIMED_WAITING: 쓰레드가 종료되지는 않았지만, 실행가능하지 않은(unrunnable) 일시정지상태. TIMED_WAITING은 일시정지시간이 지정된 경우를 의미
- TERMINATED: 쓰레드의 작업이 종료된 상태

쓰레드의 실행제어
쓰레드 프로그래밍이 어려운 이유는 동기화(synchronizaion)와 스케줄링(scheduling) 때문이다.
효율적인 멀티쓰레드 프로그램을 만들기 위해서는 정교한 스케줄링을 통해 프로세스에게 주어진 자원과 시간을 여러 쓰레드가 낭비없이 잘 사용하도록 프로그래밍 해야 한다.
쓰레드의 스케줄링을 잘하기 위해서는 쓰레드의 상태와 관련 메서드를 잘 알아야 한다.
메서드 | 설명 |
sleep() | 지정된 시간(천분의 일초 단위)동안 쓰레드를 일시정지시킨다. |
join() | 지정된 시간동안 쓰레드가 실행되도록 한다. |
interrupt() | sleep()이나 join()에 의해 일시정지상태인 쓰레드를 깨워서 실행대기상태로 만든다. |
stop() | 쓰레드를 즉시 종료시킨다. |
suspend() | 쓰레드를 일시정지시킨다. |
resume() | suspend()에 의해 일시정지상태인 쓰레드를 실행대기상태로 만든다. |
yield() | 실행 중에 자신에게 주어진 실행시간을 다른 쓰레드에게 양보(yield)하고 자신은 실행대기상태가 된다. |
sleep( )
sleep()은 지정된 시간동안 쓰레드를 멈추게 한다.
static void sleep(long millis)
static void sleep(long milils, int nanos) //millis는 밀리세컨드, nanos는 나노세컨드
sleep()에 의해 일시정지 상태가 된 쓰레드는 지정된 시간이 다 되거나 interrup()가 호출되면, InterruptedExcpetion이 발생되어 잠에서 깨어나 실행되기 상태가 된다. 그래서 sleep()을 호출할 때는 항상 try-catch문으로 예외를 처리해줘야 한다. 매번 예외 처리하는 것이 귀찮기 때문에, 아래와 같이 try-catch문까지 포함하는 새로운 메서드를 만들어서 사용하기도 한다.
void delay(long millis){
try{
Thread.sleep(millis);
}catch(InterupptedException e) {}
}
sleep( ) 예제
sleep(), yield()는 특정 쓰레드를 지정해서 멈추게 하는 것은 불가능하다.
sleep(), yield()를 실행시킨 Thread에 영향을 준다.
아래 예제에서, th1.sleep(2000)을 호출했지만, 실제 영향을 받는 것은 th1.sleep(2000)을 실행시키는 main쓰레드이다. th1와 th2는 JVM의 스케줄링에 따라 랜덤하게 끝날 수 있다.
class Ex13_8 {
public static void main(String args[]) {
ThreadEx8_1 th1 = new ThreadEx8_1();
ThreadEx8_2 th2 = new ThreadEx8_2();
th1.start(); th2.start();
try {
th1.sleep(2000); //th1의 쓰레드가 아니라 main쓰레드가 sleep한다.
} catch(InterruptedException e) {}
System.out.print("<<main 종료>>");
} // main
}
class ThreadEx8_1 extends Thread {
public void run() {
for(int i=0; i < 300; i++) System.out.print("-");
System.out.print("<<th1 종료>>");
} // run()
}
class ThreadEx8_2 extends Thread {
public void run() {
for(int i=0; i < 300; i++) System.out.print("|");
System.out.print("<<th2 종료>>");
} // run()
}
/* 출력 결과
|||||||||||||||||---------------------------------------||||||||||||-----------------
-------------------------------|||||-----||||||||----||||----------------------------
--------------------------------------------------------------------------------|||||
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|||||||||||||||||||||||||||||||-----------------------------------------------------|
||||||---------------||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
----------------------------|||||||||||||||||||||||<<th1 종료>>||||||||||||||||||||||
|||||||||||||||||<<th2 종료>><<main 종료>>
*/
위의 예제를 보면, main쓰레드가 sleep의 영향을 받았기 때문에 <<main종료>>가 제일 나중에 출력되었다. th1.sleep(2000)을 주석처리하면 <<main종료>>가 제일 먼저 출력된다.
class Ex13_8 {
public static void main(String args[]) {
ThreadEx8_1 th1 = new ThreadEx8_1();
ThreadEx8_2 th2 = new ThreadEx8_2();
th1.start(); th2.start();
// try { th1.sleep(2000); } catch(InterruptedException e) {}
System.out.print("<<main 종료>>");
} // main
}
class ThreadEx8_1 extends Thread {
public void run() {
for(int i=0; i < 300; i++) System.out.print("-");
System.out.print("<<th1 종료>>");
} // run()
}
class ThreadEx8_2 extends Thread {
public void run() {
for(int i=0; i < 300; i++) System.out.print("|");
System.out.print("<<th2 종료>>");
} // run()
}
/* 출력 결과
<<main 종료>>|||||||||||||||||||||||||||||||||||||||||||---------------||||||||||||||||
|||||||||||||||||||||||||||||||||||||||||||||||||||------------------------------------
---------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------
---------------------------------------------------------------------------||||||||||||
|||||||||||||||||||||||||<<th1 종료>>|||||||||||||||||||||||||||||||||||||||||||||||||||
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|||||||||||||||<<th2 종료>>
*/
interrupt( )
interrupt()는 쓰레드에게 작업을 멈추라고 요청한다.
단지 멈추라고 요청만 하는 것일 뿐 쓰레드를 강제로 종료시키지는 못한다.
void interrupt(): 쓰레드의 interrupt상태를 false에서 true로 변경
boolean isInterrupted(): 쓰레드의 interrupted상태를 반환
static boolean interrupted(): 현재 쓰레드의 interrupted상태를 반환 후, false로 변경
interrupt( ) 예제
사용자의 입력이 들어오면 카운트다운을 종료한다.
import javax.swing.JOptionPane;
class Ex13_9 {
public static void main(String[] args) throws Exception {
ThreadEx9_1 th1 = new ThreadEx9_1();
th1.start();
String input = JOptionPane.showInputDialog("아무 값이나 입력하세요.");
System.out.println("입력하신 값은 " + input + "입니다.");
th1.interrupt(); // interrupt()를 호출하면, interrupted상태가 true가 된다.
System.out.println("isInterrupted():"+ th1.isInterrupted()); // true
}
}
class ThreadEx9_1 extends Thread {
public void run() {
int i = 100;
//th1.interrupt()가 호출되면 isInterupted()가 true를 반환해서, 반복문이 멈춘다.
while(i!=0 && !isInterrupted()) {
System.out.println(i--);
for(long x=0;x<2500000000L;x++); // 시간 지연
}
System.out.println("카운트가 종료되었습니다.");
}
}


suspend( ), resume( ), stop( )
suspend()는 sleep()처럼 쓰레드를 멈추게 한다.
suspend()에 의해 정지된 쓰레드는 resume()을 호출해야 다시 실행대기 상태가 된다.
stop()은 호출되는 즉시 쓰레드가 종료된다.
suspend()와 stop()이 교착상태(deadlock)를 일으키기 쉽게 작성되어있으므로, 사용이 권장되지 않는다. 이 메서드들은 모두 ‘deprecated’되었다.
join( )과 yield( )
join() - 다른 쓰레드의 작업을 기다린다.
- join()은 자신의 작업 중간에 다른 쓰레드의 작업을 참여(join)시킨다는 의미로 지어진 것이다.
- 쓰레드 자신이 하던 작업을 멈추고 다른 쓰레드가 지정된 시간동안 작업을 수행하도록 할 때 사용한다. 시간을 지정하지 않으면, 해당 쓰레드가 작업을 모두 마칠 때까지 기다리게 된다.
- join()이 호출되는 부분을 try-catch문으로 감싸야 한다.
try{
th1.join(); // 현재 실행중인 쓰레드가 쓰레드 th1의 작업이 끝날때까지 기다린다.
} catch(InterruptedException e) {}
- join()도 sleep()처럼 interrupt()에 의해 대기상태에서 벗어날 수 있다.
- sleep()과 유사한데, 다른 점은 join()은 현재 쓰레드가 아닌 특정 쓰레드에 대해 동작하므로 static 메서드가 아니라는 것이다.
yield() - 다른 쓰레드에게 양보한다.
- 쓰레드 자신에게 주어진 실행시간을 다음 차례의 쓰레드에게 양보(yield)한다.
- yield()와 interrupt()를 적절히 사용해 응답성이 높은 프로그램을 구현할 수 있다.
join( ) 예제
join()을 사용하여 쓰레드 th1과 th2의 작업이 마칠 때까지 main쓰레드가 기다리게하여 소요시간을 출력할 수 있다.
join()을 사용하지 않으면 main쓰레드는 바로 종료되고, th1과 th2가 끝나기 전에 소요시간이 출력된다.
class Ex13_11 {
static long startTime = 0;
public static void main(String args[]) {
ThreadEx11_1 th1 = new ThreadEx11_1();
ThreadEx11_2 th2 = new ThreadEx11_2();
th1.start();
th2.start();
startTime = System.currentTimeMillis();
try {
th1.join(); // main쓰레드가 th1의 작업이 끝날 때까지 기다린다.
th2.join(); // main쓰레드가 th2의 작업이 끝날 때까지 기다린다.
} catch(InterruptedException e) {}
System.out.print("소요시간:" + (System.currentTimeMillis() - Ex13_11.startTime));
} // main
}
class ThreadEx11_1 extends Thread {
public void run() {
for(int i=0; i < 300; i++) {
System.out.print(new String("-"));
}
} // run()
}
class ThreadEx11_2 extends Thread {
public void run() {
for(int i=0; i < 300; i++) {
System.out.print(new String("|"));
}
} // run()
}
/*
출력 결과
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||----------------
-----------------------------------------------------------------------|||||||||||||||||
||||||||||||||||||||||------||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|------------------------------------------|||||||||||||||||||||||||||||||||||||||||||||
||||||||||||||||||||||||||||||-----------------------------------------------------|||||
|||||||||||||||||||||||||||||-----------------------------------------|---||||||||||||||
||||--------------------------------------------------------------------소요시간:6
*/
쓰레드의 동기화(synchronization)
멀티쓰레드 프로세스의 경우 여러 쓰레드가 같은 프로세스 내의 자원을 공유해서 작업하기 때문에 서로의 작업에 영향을 주게 된다.
한 쓰레드가 특정 작업을 끝마치기 전까지 다른 쓰레드에 의해 방해받지 않도록 하는 것이 필요하다. 그래서 도입된 개념이 바로 ‘임계 영역(critical section)’과 ‘잠금(락, lock)’이다.
공유 데이터를 사용하는 코드 영역을 임계 영역으로 지정해놓고, 공유 데이터(객체)가 가지고 있는 lock을 획득한 단 하나의 쓰레드만 이 영역 내의 코드를 수행할 수 있게 한다. 그리고 해당 쓰레드가 임계 영역 내의 모든 코드를 수행하고 벗어나서 lock을 반납해야만 다른 쓰레드가 반납된 lock을 획득하여 임계 영역의 코드를 수행할 수 있게 된다.
한 쓰레드가 진행 중인 작업을 다른 쓰레드가 간섭하지 못하도록 막는 것을 ‘쓰레드의 동기화(synchronization)’라고 한다.
synchronized를 이용한 동기화
synchronized 키워드는 임계 영역을 설정하는데 사용된다. 아래 두 가지 방식이 있다.
1) 메서드 전체를 임계 영역으로 지정
public synchronized void calcSum(){
//...
}
2) 특정한 영역을 임계 영역으로 지정
//참조변수는 락(lock)을 걸고자하는 객체를 참조하는 것이어야 한다.
synchronized(객체의 참조변수){
//...
}
모든 객체는 lock을 하나씩 가지고 있으며, 해당 객체의 lock을 가지고 있는 쓰레드만 임계 영역의 코드를 수행할 수 있다. 그리고 다른 쓰레드들은 lock을 얻을 때까지 기다리게 된다.
임계 영역은 멀티쓰레드 프로그램의 성능을 좌우하기 때문에 가능하면 메서드 전체에 락을 거는 것보다 synchronized블럭으로 임계 영역을 최소화해서 보다 효율적인 프로그램이 되도록 해야 한다.
wait( )과 notify( )
synchronized로 동기화해서 공유 데이터를 보호하는 것 까지는 좋은데, 특정 쓰레드가 객체의 락을 가진 상태로 오랜 시간을 보내지 않도록 하는 것도 중요하다.
동기화된 임계 영역의 코드를 수행하다가 작업을 더 이상 진행할 상황이 아니면, 일단 wait()을 호출하여 쓰레드가 락을 반납하고 기다리게 한다. 그러면 다른 쓰레드가 락을 얻어 해당 객체에 대한 작업을 수행할 수 있게 된다. 나중에 작업을 진행할 수 있는 상황이 되면 notify()를 호출해서, 작업을 중단했던 쓰레드가 다시 락을 얻어 작업을 진행할 수 있게 한다.
오래 기다린 쓰레드가 락을 얻는다는 보장이 없다.
wait()이 호출되면, 실행 중이던 쓰레드는 해당 객체의 대기실(waiting pool)에서 통지를 기다린다. notify()가 호출되면, 해당 객체의 대기실에 있던 모든 쓰레드 중에서 임의의 쓰레드만 통지를 받는다. notifyAll()은 기다리고 있는 모든 쓰레드에게 통보를 하지만, 그래도 lock을 얻을 수 있는 것은 하나의 쓰레드일 뿐이고, 나머지는 다시 lock을 기다리는 신세가 된다.
wait(), notify(), notifyAll()은 특정 객체에 대한 것이므로 Object 클래스에 정의되어있다.
wait(), notify(), notifyAll()은 동기화 블록(synchronized블록) 내에서만 사용할 수 있다.
wait()은 notify() 또는 notifyAll()이 호출될 때까지 가다리지만, 매개변수가 있는 wait()은 지정된 시간동안만 기다린다. 즉, 지정된 시간이 지난 후에 자동적으로 notify()가 호출되는 것과 같다.
waiting pool은 객체마다 존재한다.
notifyAll()이 호출된다고 해서 모든 객체의 waiting pool에 있는 쓰레드가 깨워지는 것은 아니다. notifyAll()이 호출된 객체의 waiting pool에 대기 중인 쓰레드만 해당된다.
'Programming Language > 자바 기초' 카테고리의 다른 글
14. 람다와 스트림 (0) | 2022.03.14 |
---|---|
12. 지네릭스, 열거형, 애너테이션 (0) | 2022.03.14 |
11. 컬렉션 프레임웍 (0) | 2022.03.14 |
10. 날짜와 시간 & 형식화 (0) | 2022.03.14 |
9. java.lang 패키지와 유용한 클래스 (0) | 2022.03.14 |