


쓰레드를 생성 후 start()를 하면 바로 실행되는 것 X
실행대기열(Queue 형태)에 저장되어 자신의 차례까지 기다린다
실행대기상태에 있다가 자신의 차례가 되면 실행 상태
주어진 실행 시간이 다되거나 yield()를 만나면 다시 실행 대기상태가 되고 다음차례의 쓰레드가 실행됨
실행 중에 suspend, sleep, wait,join,I/O block에 의해 일시정지상태가 될수 있다. 여기서 I/O block은 입출력작업에서 발생하는 지연상태인데 이 경우 일시정지상태에 있다가 사용자가 입력을 마치면 다시 실행상태가 됨
지정된 일시정지시간이 다되거나(time-out), notify(), resume(), interrupt()가 호출되면 일시정지 상태에서 벗어나 다시 실행대기열에 저장되어 차레 기다림
실행을 모두 마치거나 , stop()이 호출되면 소멸됨
동기화와 스케쥴링이 중요
우선순위 이용만으로는 부족
-> 정교한 스케쥴링을 통해 프로세스에게 주어진 자원과 시간을 여러 쓰레드가 낭비없이 사용하도록 프로그래밍 해야 함
밑에 있는 메서드들을 잘 사용해보자

지정된 시간동안 쓰레드를 멈추게 한다
특정 쓰레드를 지정해서 멈추게 하는 것은 불가!!!!!(코드를 실행하는 쓰레드가 sleep)
// 밀리세컨드(1000분의 일초), 나노세컨드(10억분의 일초)
static void sleep(long millis)
static void sleep(long millis, int nanos)
try{
Thread.sleep(1,500000); // 쓰레드를 0.0015초 동안 멈추게 한다
} catch(InterruptedException e){ }
sleep()에 의해 일시정지된 상태의 쓰레드는 지정 시간이 다 되거나, interrupt()가 호출되면, InterruptedException이 발생해 실행 대기 상태가 된다
-> try-catch로 예외를 처리해주어야 함
// 메서드 만들어 사용하기도 함
void delay(long millis){
try{
Thread.sleep(millis);
} catch(InterruptedException e) {}
}
package ch13;
public 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 잠자게??? X , Thread.sleep이 바람직
} catch(InterruptedException e) {}
System.out.print("<<main 종료>> ");
}
}
class ThreadEx8_1 extends Thread{
public void run() {
for(int i = 0; i < 300; i++) System.out.print("-");
System.out.print("<<th1 종료>>");
}
}
class ThreadEx8_2 extends Thread {
public void run() {
for(int i=0;i<300;i++) System.out.print("|");
System.out.print("<<th2 종료>>");
}
}
.....
<th1 종료><th2 종료>< main 종료>
sleep()은 항상 현재 실행 중인 쓰레드에 대해서만 작동하기에 main쓰레드가 sleep
만약 위 코드에서 sleep()이 없다면 메인 메서드는 바로 main종료를 출력함!!!
진행 중인 쓰레드의 작업이 끝나기 전에 취소시켜야 할 경우
-> 강제 종료X, 멈추라고 요청
interrupted()는 쓰레드에 대해 interrupt()가 호출되었는지 알려줌
Thread th = new Thread();
th.start();
...
th.interrupt(); // 쓰레드 th에 interrupt()을 호출
...
class MyThread extends Thread{
public void run(){
while(!interrupted()){
// interrupted()의 결과가 false인 동안 반복
...
}
}
}
sleep(), wait(), join()에 의해 일시정지 상태에 있을 때, 쓰레드에 대해 interrupt()를 호출하면 Interrupted Exception이 발생하면서 쓰레드가 실행대기상태(Runnable)로 바뀜
-> 멈춰있는 쓰레드를 깨워서 실행 가능 상태로
package ch13;
import javax.swing.JOptionPane;
public 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();
System.out.println("isInterrupted():" + th1.isInterrupted());
}
}
class ThreadEx9_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("카운트가 종료되었습니다.");
}
}
카운트 중간에 값을 입력하면 카운트가 다 되지 않더라도 카운트가 종료됨
쓰레드를 멈추게 함호출 즉시 쓰레드 종료suspend와 stop은 deprecated이고 DeadLock 발생시키기 좋아 사용 권장 X
package ch13;
public class Ex13_10 {
public static void main(String[] args) {
RunImpIEx10 r = new RunImpIEx10();
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(); // th1을 잠시 중단.
Thread.sleep(2000);
th2.suspend();
Thread.sleep(3000);
th1.resume(); // 쓰레드 th1이 다시 동작하도록 한다.
Thread.sleep(3000);
th1.stop(); // 쓰레드 th1을 강제 종료시킨다.
th2.stop();
Thread.sleep(2000);
th3.stop();
} catch(InterruptedException e) { }
}
}
class RunImpIEx10 implements Runnable{
public void run() {
while(true) {
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch(InterruptedException e) { }
}
}
}
쓰레드 자신이 하던 작업을 잠시 멈추고 다른 쓰레드가 지정된 시간동안 작업을 수행하도록 함
void join()
void join(long millis)
void join(long millis, int nanos)
// 조인도 sleep 처럼 interrupt에 의해 대기 상태에 벗어날 수 있고, try-catch로 감싸야함
// sleep과 다르게 join()은 현재 쓰레드가 아닌 특정 쓰레드에 대해 동작
try{
th1.join(); // 현재 실행중인 쓰레드가 쓰레드 th1의 작업이 끝날때까지 기다린다
} catch ( InterruptedException e) { }
쓰레드 자신에게 주어진 실행시간을 다음 차례에 쓰레드에게 양보
-> 스케쥴러에 의해 1초의 실행시간을 할당받은 쓰레드가 0.5초의 시간동안 작업한 상태에서 yield()가 호출되면 0.5초 포기하고 실행 대기 상태로 된다
package ch13;
public 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));
}
}
class ThreadEx11_1 extends Thread{
public void run() {
for(int i=0; i < 300; i++) {
System.out.print(new String("-"));
}
}
}
class ThreadEx11_2 extends Thread{
public void run() {
for(int i = 0; i < 300; i++) {
System.out.print(new String("|"));
}
}
}
싱글쓰레드는 하나의 쓰레드로 작업하기에 별 문제 X
멀티쓰레드는 같은 프로세스 내에서 작업을 공유하기에 서로 작업의 영향
ex) 쓰레드A가 작업하던 공유데이터를 쓰레드B가 임의로 변경했다면 다시 쓰레드A이 제어권을 받아서 나머지 작업을 마쳤을 때 의도한 것과 다른 결과
한 쓰레드가 특정 작업을 끝마치기 전까지 다른 쓰레드에 의해 방해 받지 않도록 하는 것
-> 임계영역(Critical section)과 잠금(Lock)
공유데이터를 사용하는 코드 영역을 임계영역으로 지정하고, 공유 데이터(객체)가 가지고 있는 lock을 획득한 단 하나의 쓰레드만 코드를 수행할 수 있도록 함
쓰레드 동기화 -> 한 쓰레드가 진행 중인 작업을 다른 쓰레드가 간섭하지 못하도록 막는 것
lock 획득과 반납이 모두 자동적으로 이루어지므로 임계영역만 지정해 주면 됨
성능을 고려했을 때는 메서드 전체 락 보다는 synchronized블럭으로 임계영역을 최소화하는 것이 효율적
// 1. 메서드 전체를 임계 영역으로 지정
/* 쓰레드는 synchronized메서드가 호출된 시점부터 해당 메서드가 포함된 객체의 lock을 얻어
작업을 수행하다가 종료시 lock 반환 */
public synchronized void calcSum(){
//...
}
// 2. 특정 영역을 임계 영역으로 지정
/* synchronized블럭의 영역 안으로 들어가면서부터 쓰레드는 지정된 객체의 lock을 얻고,
블럭을 벗어나면 lock을 반납 */
synchronized(객체의 참조변수){
//...
}
package ch13;
public class Ex13_12 {
public static void main(String[] args) {
Runnable r = new RunnableEx12();
new Thread(r).start(); // Thread 그룹에 의해 참조되므로 gc의 대상이 아니다.
new Thread(r).start(); // Thread 그룹에 의해 참조되므로 gc의 대상이 아니다.
}
}
class Account
{
// private으로 해야 동기화가 의미가 있다. 직접적으로 변수에 접근하는 것은 막기 힘들다(중요)
private int balance = 1000;
public int getBalance() {
return balance;
}
public void withdraw(int money) {
if(balance >= money) {
// 오류 찾기 위해 넣은거 신경 X
try { ThreadEx11_2.sleep(1000);} catch(InterruptedException e) {}
balance -= money;
}
}
}
class RunnableEx12 implements Runnable{
Account acc = new Account();
public void run() {
while(acc.getBalance() > 0) {
// 100,200,300 중의 한 값을 임의로 선택해서 출금
int money = (int) ( Math.random() * 3 + 1) * 100;
acc.withdraw(money);
System.out.println("balance:" + acc.getBalance());
}
}
}
// 잔고가 음수가 됨
// 한 쓰레드가 if문의 조건식을 통과하고 출금하기 바로 직전에 다른 쓰레드가 끼어들어서 출금을 먼저 했기 때문
balance:700
balance:400
balance:200
balance:0
balance:-100
withdraw 메서드를 synchronized로 동기화 하였을 경우엔 밑과 같이 예상한 결과가 출력 됨
balance:800
balance:700
balance:400
balance:100
balance:100
balance:100
balance:100
balance:100
balance:100
balance:100
balance:100
balance:0
balance:0
synchronized로 동기화를 하여 공유 데이터를 보호 하는 것도 좋지만, 특정 쓰레드가 락을 가진 상태로 오래 시간을 보내지 않는 것도 중요 -> 이 상황 개선하기 위해 wait와 notify 사용(Object 클래스에 정의)
동기화 블럭 내에서만 사용 가능
void wait(), void wait(long timeout) - 메서드 호출 시 실행 중이던 쓰레드는 해당 객체의 waiting pool에서 대기(매개변수 있는 것은 지정된 시간 지난 후 자동으로 notify 호출되는 것)notify() - 메서드 호출 시 waiting pool의 쓰레드 중 임의의 쓰레드를 깨움notifyAll() - 메서드 호출 시 waiting pool의 모든 쓰레드를 깨움빵을 사려고 빵집 앞에 줄을 서있는 경우, 자신의 차례가 되엇는데도 자신이 원하는 빵이 나오지 않았으면, 다음 사람에게 순서를 양보하고 기다리다가 자신이 원하는 빵이 나오면 통보를 받고 빵을 사가는 것
그러나 오래 기다린 쓰레드가 락을 얻는 다는 보장은 X
class Account{
int balance = 1000;
public synchronized void withdraw(int money){
while(balance < money){
try{
wait();
} catch(InterruptedException e) { }
}
balance -= money;
}
public synchronized void deposit(int money){
balance += money;
notify();
}
}
1. 한 쓰레드(잔고가 적은 경우)가 락을 가지고 withdraw 문 안에 들어감
2. 들어가지만 잔고가 적어 돈을 출금할 수 없으므로 while 문
안에 락을 가진 채로 갇히게 된다
3. 이때 wait를 통해 현재 갇힌 쓰레드의 락을 풀고 어카운트 객체의
대기실(waiting pool)로 향하게 한다
4. 다른 객체가 락을 가지고 deposit을 통해 출금액 보다 큰 돈을 입금하고
난 뒤 notify로 알리면 맨 처음에 갇힌 쓰레드가 withdraw 시도
5. 만약 돈이 또 모자르다면 또 락을 반납한 후 wating pool로 이동
waiting pool은 객체마다 존재하는 것이므로 notifyAll()이 호출된 다고 모든 객체의 waiting pool에 있는 쓰레드가 깨워지는 것은 아님
package ch13;
import java.util.ArrayList;
class Customer implements Runnable{
private Table2 table;
private String food;
Customer(Table2 table, String food){
this.table =table;
this.food = food;
}
public void run() {
while(true) {
try { Thread.sleep(10);} catch(InterruptedException e) {}
String name = Thread.currentThread().getName();
if(eatFood())
System.out.println(name + "ate a " + food);
else
System.out.println(name + "failed to eat. : (");
} // while
}
boolean eatFood() { return table.remove(food); }
}
class Cook implements Runnable{
private Table2 table;
Cook(Table2 table) { this.table = table;}
public void run() {
while(true) {
int idx = (int)(Math.random()*table.dishNum());
table.add(table.dishNames[idx]);
try { Thread.sleep(100);} catch(InterruptedException e) {}
}
}
}
class Table2{
String[] dishNames= {"donut", "donut", "burger"};
final int MAX_FOOD = 6;
private ArrayList<String> dishes = new ArrayList<>();
public synchronized void add(String dish) {
if(dishes.size() >= MAX_FOOD)
return;
dishes.add(dish) ;
System.out.println("Dishes :" + dishes.toString());
}
public boolean remove(String dishName) {
synchronized(this) {
while(dishes.size() == 0) {
String name = Thread.currentThread().getName();
System.out.println(name + "is waiting.");
try { Thread.sleep(500);} catch(InterruptedException e) {}
}
for(int i = 0; i<dishes.size();i++)
if(dishName.equals(dishes.get(i))) {
dishes.remove(i);
return true;
}
}
return false;
}
public int dishNum() { return dishNames.length; }
}
public class Ex13_14 {
public static void main(String[] args) throws Exception{
Table2 table = new Table2(); // 여러 쓰레드가 공유하는 객체
new Thread(new Cook(table), "COOK").start();
new Thread(new Customer(table, "donut"), "CUST1").start();
new Thread(new Customer(table, "burger"), "CUST2").start();
Thread.sleep(5000);
System.exit(0);
}
}
Dishes : [burger]
CUST2 ate a burger
CUST1 failed to eat. :( -> 도넛이 없어서 먹지 못함
CUST2 is waiting
CUST2 is waiting
CUST2 is waiting
...
손님 쓰레드가 테이블 객체의 Lock을 계속 가지고 있기 떄문
요리사가 음식을 새로 추가하려해도 테이블 객체의 lock을 얻을 수 없어 불가능
-> 이럴때 wait(), notify() 이용
package ch13;
import java.util.ArrayList;
class Customer implements Runnable{
private Table2 table;
private String food;
Customer(Table2 table, String food){
this.table =table;
this.food = food;
}
public void run() {
while(true) {
try { Thread.sleep(500);} catch(InterruptedException e) {}
String name = Thread.currentThread().getName();
table.remove(food);
System.out.println(name + " ate a" + food);
} // while
}
}
class Cook implements Runnable{
private Table2 table;
Cook(Table2 table) { this.table = table;}
public void run() {
while(true) {
int idx = (int)(Math.random()*table.dishNum());
table.add(table.dishNames[idx]);
try { Thread.sleep(100);} catch(InterruptedException e) {}
}
}
}
class Table2{
String[] dishNames= {"donut", "donut", "burger"};
final int MAX_FOOD = 6;
private ArrayList<String> dishes = new ArrayList<>();
public synchronized void add(String dish) {
while(dishes.size() >= MAX_FOOD) {
String name = Thread.currentThread().getName();
System.out.println(name + "is waiting");
try {
wait(); //COOK 쓰레드를 기다리게 함
Thread.sleep(500);
} catch(InterruptedException e) {}
}
dishes.add(dish) ;
notify();
System.out.println("Dishes :" + dishes.toString());
}
public void remove(String dishName) {
synchronized(this) {
String name = Thread.currentThread().getName();
while(dishes.size() == 0) {
System.out.println(name+ " is waiting");
try {
wait(); // CUST 쓰레드를 기다리게 한다
Thread.sleep(500);
} catch(InterruptedException e) {}
}
while(true) {
for(int i = 0; i < dishes.size();i++) {
if(dishName.equals(dishes.get(i))) {
dishes.remove(i);
notify(); // 음식 먹었다고 요리사 에게 통보, waiting pool에 없을 수도 있
return;
}
}
// 원하는 음식이 없는 경우.
try {
System.out.println(name + " is waiting");
wait(); // 원하는 음식이 없는 CUST쓰레드를 기다리게 한다
Thread.sleep(500);
} catch(InterruptedException e) { }
}
}
}
public int dishNum() { return dishNames.length;}
}
public class Ex13_14 {
public static void main(String[] args) throws Exception{
Table2 table = new Table2(); // 여러 쓰레드가 공유하는 객체
new Thread(new Cook(table), "COOK").start();
new Thread(new Customer(table, "donut"), "CUST1").start();
new Thread(new Customer(table, "burger"), "CUST2").start();
Thread.sleep(5000);
System.exit(0);
}
}
Dishes:[donut]
... 중간 생략 ..
Dishes:[donut,donut,donut,donut,donut, donut]
Cook is waiting
CUST2 is waiting
CUST1 ate a donut
Dishes:[donut,donut,donut,donut,donut,donut]
CUST2 is waiting // 원하는 음식이 없어 기다림
Cook is waiting // 테이블이 가득차서 요리사가 기다림
CUST1 ate a donut // 소비하여 notify() 호출
CUST2 is waiting // 요리사가 아닌 손님이 통지를 받고 다시 기다림
CUST1 ate a donut // 테이블에 음식이 소비되어 notify() 호출됨
Dishes : [donut, donut, donut, donut, donut ] // 요리사가 통지를 받고 음식 추가
wait() 과 notify()의 대상이 불분명
ex) 대기실에 요리사와 손님이 둘다 있는 경우 음식 소비 시 손님이 notify 받으면 의미 없음 -> 이럴 때 Lock하고 Condition 사용(선별적 통지가 가능)
운이 없으면 요리사 쓰레드는 계속 통지를 받지 못하고 wait 상태에 있는데, 이것을 기아(starvation) 현상이라고 한다.
이 현상을 막으려면, notify()대신 notifyAll()을 사용해야 한다. 일단 모든 쓰레드에게 통지를 하면, 손님 쓰레드는 다시 waiting pool에 들어가더라도 요리사 쓰레드는 결국 lock을 얻어서 작업을 진행할 수 있기 때문이다.
여러 쓰레드가 lock을 얻기 위해 서로 경쟁하는 것을 경쟁 상태(race condition)라고 하는데, 해당 코드에서도 발생하는 것을 볼 수 있다.
notifyAll()로 요리사 쓰레드의 기아현상은 막았지만, 손님 쓰레드까지 통지를 받아서 불필요하게 요리사 쓰레드와 lock을 얻기 위해 경쟁하게 된다.
이 경쟁 상태를 개선하기 위해서는 요리사 쓰레드와 손님 쓰레드를 구별해서 통지하는 것이 필요하다.