chapter12. 멀티 스레드. 


12.6 스레드 상태 제어

- 실행 중인 스레드의 상태를 변경하는 것을 말한다.

// 동영상을 종료 or 일시정지

- 상태 변화를 가져오는 메소드의 종류


메소드

설명

interrupt()

일시 정지 상태의 스레드에서 InterruptedException 예외를 발생시켜, 예외 처리코드(catch)에서 실행 대기 상태로 가거나 종료 상태로 갈 수 있도록 한다

notify()

notifyAll()

동기화 블록 내에서 wait() 메소드에 의해 일시 정지 상태에 있는 스레드를 실행 대기 상태로 만든다

resume()

suspend() 메소드에 의해 일시 정지 상태에 있는 스레드를 실행 대기 상태로 만든다 Deprecated (대신 notify(), notifyAll 사용)

sleep(long millis)

sleep(long millis, int nanos)

주어진 시간 동안 스레드를 일시 정지 상태로 만든다. 주어진 시간이 지나면 자동적으로 실행 대기 상태가 된다

join()

join(long millis)

join(long millis, int nanos)

join() 메소드를 호출한 스레드는 일시 정지 상태가 된다. 실행 대기 상태로 가려면, join() 메소드를 멤버로 가지는 스레드가 종료되거나, 매개값으로 주어진 시간이 지나야 한다

wait()

wait(long millis)

wait(long millis, int nanos)

동기화(synchronized) 블록 내에서 스레드를 일시 정지 상태로 만든다. 매개값으로 주어진 시간이 지나면 자동적으로 실행 대기 상태가 된다. 시간이 주어지지 않으면 notify(), notifyAll() 메소드에 의해 실행 대기 상태로 갈 수 있다

suspend()

스레드를 일시 정지 상태로 만든다. resume() 메소드를 호출하면 다시 실행 대기 상태가 된다. - Deprecated (대신 wait() 사용)

yield()

실행 중에 우선순위가 동일한 다른 스레드에게 실행을 양보하고 실행 대기 상태가 된다.

stop()

스레드를 즉시 종료시킨다. - Deprecated



12.6.1 주어진 시간동안 일시정지(sleep())

- 얼마 동안 일시정지 상태로 있을 것인지, 밀리세컨드(1/1000) 단위로 지정

- 일시정지 상태에서 interrupt() 메소드가 호출되면 InterruptedException 발생



3초 주기로 10번 비프음 발생

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package sec06.exam01_sleep;
 
import java.awt.Toolkit;
 
public class SleepExample {
    public static void main(String[] args) {
        Toolkit toolkit = Toolkit.getDefaultToolkit(); // 비프음을 발생시키는 메서드    
        for(int i=0; i<10; i++) {
            toolkit.beep();
            try {
                Thread.sleep(3000); // 3초 동안 메인 스레드를 일시 정지 상태를 만듬
            } catch(InterruptedException e) {            
            }        
        }    
    }
}
cs


12.6.2 다른 스레드에게 실행 양보(yield())



// 1. 다음 예제에서는 처음 실행 후 3초 동안은 ThreadA와 ThreadB가 번갈아가며 실행된다.

// 2. 3초 뒤에 메인 스레드가 ThreadA의 work 필드를 false로 변경함으로써 ThreadA는 yield() 메소드를 호출한다

// 따라서 이후 3초 동안에는 ThreadB가 더 많은 실행 기회를 얻게 된다.

// 3. 메인 스레드는 3초 뒤에 다시 ThreadA의 work 필드를 true로 변경해서 ThreadA와 ThreadB가 번갈아가며 실행하도록 한다.

// 4. 마지막으로 메인 스레드는 3초 뒤에 ThreadA와 ThreadB의 stop 필드를 true로 변경해서 두 스레드가 반복 작업을 중지하고 종료하도록 한다.


ThreadA

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package sec06.exam02_yield;
 
public class ThreadA extends Thread {    
    public boolean stop = false;
    public boolean work = true;
    
    public void run() {
        while(!stop) {
            if(work) {
                System.out.println("ThreadA 작업 내용");
            } else {
                Thread.yield();
            }
        }
        System.out.println("ThreadA 종료");
    }
}
cs


ThreadB

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package sec06.exam02_yield;
 
public class ThreadB extends Thread {    
    public boolean stop = false;
    public boolean work = true;
    
    public void run() {
        while(!stop) {
            if(work) {
                System.out.println("ThreadB 작업 내용");
            } else {
                Thread.yield();
            }
        }
        System.out.println("ThreadB 종료");
    }
}
cs


스레드 실행 양보 예제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package sec06.exam02_yield;
 
public class YieldExample {
    public static void main(String[] args) {
        ThreadA threadA = new ThreadA(); // ThreadA, ThreadB 모두 실행
        ThreadB threadB = new ThreadB();    
        threadA.start();        
        threadB.start();
        
        try { Thread.sleep(3000); } catch (InterruptedException e) {}
        threadA.work = false; // ThreadB만 실행
        
        try { Thread.sleep(3000); } catch (InterruptedException e) {}
        threadA.work = true; // ThreadA, ThreadB 모두 실행
        
        try { Thread.sleep(3000); } catch (InterruptedException e) {}
        threadA.stop = true; // ThreadA, ThreadB 모두 종료
        threadB.stop = true; // stop() 메소드는 강제 종료이기 때문에 프로그램이 불안정 상태일 수도있다.
// 걍 필드값을 true로 만들어서 빨리 끝내게 한다
    }
}
cs


12.6.3 다른 스레드의 종료를 기다림(join())

- 계산 작업을 하는 스레드가 모든 계산 작업을 마쳤을 때, 계산 결과값을 받아 이용하는 경우에 주로 사용

- join() 메소드를 실행하는 메소드가 일시정지가 되고, 다른 스레드의 종료를 기다린다.




1부터 100까지 합을 계산하는 스레드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package sec06.exam03_join;
 
public class SumThread extends Thread {    
    private long sum;
    
    public long getSum() {
        return sum;
    }
 
    public void setSum(long sum) {
        this.sum = sum;
    }
 
    public void run() {
        for(int i=1; i<=100; i++) {
            sum+=i;
        }
    }
}
cs



다른 스레드가 종료될 때까지 일시정지 상태를 유지

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package sec06.exam03_join;
 
public class JoinExample {
    public static void main(String[] args) {
        SumThread sumThread = new SumThread();
        sumThread.start();
        System.out.println("1~100 합: " + sumThread.getSum()); // 계산이 끝나지도 않았는데, 출력해버리면 엉뚱한 값 나올수도 있음  
 
        try {
            sumThread.join(); // sumThread가 종료될 때까지 메인 스레드를 일시정지시킴
        } catch (InterruptedException e) {
        }
        System.out.println("1~100 합: " + sumThread.getSum());
    }
}
cs



12.6.4 스레드 간 협업(wait(), notify(), notifyAll())

// 경우에 따라서는 두 개의 스레드를 교대로 번갈아가며 실행해야 할 경우가 있다. 정확한 교대 작업이 필요할 경우,

// 자신의 작업이 끝나면 상대방 스레드를 일시 정지 상태에서 풀어주고, 자신은 일시정지 상태로 만드는것이다.

// 이 방법의 핵심은 공유 객체에 있다.

// 공유 객체는 두 스레드가 작업할 내용을 각각 동기화 메소드로 구분해 놓는다.

// 한 스레드가 작업을 완료하면 notify() 메소드를 호출해서 일시 정지 상태에 있는 다른 스레드를 실행 대기 상태로 만들고,

// 자신은 두 번 작업을 하지 않도록 wait() 메소드를 호출하여 일시 정지 상태로 만든다.


// 만약 wait() 대신 wait(long timeout)이나, wait(long timeout, int nanos)를 사용하면 

notify()를 호출하지 않아도 지정된 시간이 지나면 스레드가 자동적으로 실행 대기 상태가 된다.


// notify() 메소드와 동일한 역할을 하는 notifyAll() 메소드도 있는데, 

notify()는 wait()에 의해 일시 정지된 스레드 중 한 개를 실행 대기 상태로 만들고, 

notifyAll()는 wait()에 의해 일시 정지된 모든 스레드들을 실행 대기 상태로 만든다.


// 이 메소드들은 Thread 클래스가 아닌 Object 클래스에 선언된 메소드이므로 모든 공유 객체에서 호출이 가능하다.

// 주의할 점은 이 메소드들은 동기화 메소드 또는 동기화 블록 내에서만 사용할 수 있다.


- 동기화 메소드 또는 블록에서만 호출 가능한 Object의 메소드

// Thread의 메소드가 아니다. 모든 객체가 가지고 있는 메소드이다 





- wait()

// 호출한 스레드는 일시정지가 된다.

// 다른 스레드가 notify() 또는 notifyAll()을 호출하면 실행대기 상태가 된다


- wait(long timeout), wait(long timeout, int nanos)

// notify가 호출되지 않아도 시간이 지나면 스레드가 자동적으로 실행대기 상태가 된다


- 두개의 스레드가 교대로 번갈아 가며 실행해야할 경우에 주로 사용



두 스레드의 작업 내용을 동기화 메소드로 작성한 공유 객체

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package sec06.exam04_wait_notify;
 
public class WorkObject {
    public synchronized void methodA() {
        System.out.println("ThreadA의 methodA() 작업 실행");
        notify(); // 일시정지 상태에 있는 ThreadB를 실행 대기 상태로 만듬
        try {
            wait(); // ThreadA를 일시정지 상태로 만듬
        } catch (InterruptedException e) {
        }
    }
    
    public synchronized void methodB() {
        System.out.println("ThreadB의 methodB() 작업 실행");
        notify(); // 일시정지 상태에 있는 ThreadA를 실행 대기 상태로 만듬
        try {
            wait(); // ThreadB를 일시정지 상태로 만듬
        } catch (InterruptedException e) {
        }
    }
}
cs


 ThreadA

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package sec06.exam04_wait_notify;
 
public class ThreadA extends Thread {
    private WorkObject workObject;
 
    public ThreadA(WorkObject workObject) {
        this.workObject = workObject;
    }
    
    @Override
    public void run() {
        for(int i=0; i<10; i++) {
            workObject.methodA();
        }
    }
}
cs

 ThreadB

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package sec06.exam04_wait_notify;
 
public class ThreadB extends Thread {
    private WorkObject workObject;
 
    public ThreadB(WorkObject workObject) {
        this.workObject = workObject;
    }
    
    @Override
    public void run() {
        for(int i=0; i<10; i++) {
            workObject.methodB();
        }
    }
}
cs


두 스레드를 생성하고 실행하는 메인 스레드

1
2
3
4
5
6
7
8
9
10
11
12
13
package sec06.exam04_wait_notify;
 
public class WaitNotifyExample {
    public static void main(String[] args) {
        WorkObject sharedObject = new WorkObject();
        
        ThreadA threadA = new ThreadA(sharedObject);
        ThreadB threadB = new ThreadB(sharedObject);
        
        threadA.start();
        threadB.start();
    }
}
cs


두 스레드의 작업 내용을 동기화 메소드로 작성한 공유 객체

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package sec06.exam05_wait_notify;
 
public class DataBox {
    private String data;
    
    public synchronized String getData() { // 공유 객체의 데이터 읽기

        if(this.data == null) {
            try {
                wait();
            } catch(InterruptedException e) {}
        }
        String returnValue = data;
        System.out.println("ConsummerThread가 읽은 데이터: " + returnValue);
        data = null;
        notify();
        return returnValue;
    }
    
    public synchronized void setData(String data) { // 공유 객체에 데이터 저장
        if(this.data != null) {
            try {
                wait();
            } catch(InterruptedException e) {}
        }
        this.data = data;
        System.out.println("ProducerThread가 생성한 데이터: " + data);
        notify();
    }
}
cs


 데이터를 생산(저장)하는 스레드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package sec06.exam05_wait_notify;
 
public class ProducerThread extends Thread {
    private DataBox dataBox;
 
    public ProducerThread(DataBox dataBox) {
        this.dataBox = dataBox;
    }
    
    @Override
    public void run() {
        for(int i=1; i<=3; i++) {
            String data = "Data-" + i;
            dataBox.setData(data);
        }// 공유 객체에 데이터 저장
    }
}
cs

 데이터를 소비하는(읽는) 스레드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package sec06.exam05_wait_notify;
 
public class ConsumerThread extends Thread {
    private DataBox dataBox;
 
    public ConsumerThread(DataBox dataBox) {
        this.dataBox = dataBox;
    }
    
    @Override
    public void run() {
        for(int i=1; i<=3; i++) {
            String data = dataBox.getData();
        } // 공유 객체의 데이터 읽기
    }
}
cs


두 스레드를 생성하고 실행하는 메인 스레드

1
2
3
4
5
6
7
8
9
10
11
12
13
package sec06.exam05_wait_notify;
 
public class WaitNotifyExample {
    public static void main(String[] args) {
        DataBox dataBox = new DataBox();
        
        ProducerThread producerThread = new ProducerThread(dataBox);
        ConsumerThread consumerThread = new ConsumerThread(dataBox);
        
        producerThread.start();
        consumerThread.start();
    }
}
cs 


12.6.5 스레드의 안전한 종료(stop 플래그, interrupt())
- 경우에 따라서는 실행 중인 스레드를 즉시 종료할 필요가 있다.
- stop() 메소드
// 스레드를 즉시 종료시킨다.
// 스레드를 갑자기 종료하게 되면 사용 중이던 자원들이 불안전한 상태로 남겨진다.
// deprecated

- stop 플래그를 이용하는 방법

// stop 플래그로 run() 메소드의 정상 종료를 유도한다 (일시정지는 종료는 못함)



 무한 반복해서 출력하는 스레드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package sec06.exam06_stop;
 
public class PrintThread1 extends Thread {
    private boolean stop;
    
    public void setStop(boolean stop) {
      this.stop = stop;
    }
    
    public void run() {    
        while(!stop) {
            System.out.println("실행 중");
        }    
        System.out.println("자원 정리");
        System.out.println("실행 종료");
    }
}
cs

 1초 후 출력 스레드를 중지시킴. 실행 클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package sec06.exam06_stop;
 
public class StopFlagExample {
    public static void main(String[] args)  {
        PrintThread1 printThread = new PrintThread1();
        printThread.start();
        
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
        }
        
        printThread.setStop(true);
    }
}
cs


* interrupt() 메소드를 이용하는 방법

- 일시정지 상태일 경우 InterruptedException 을 발생시킴

- 실행대기 또는 실행상태에서는 InterruptedException이 발생하지 않음 (무조건 일시정지 상태일때만 발생함)



- 일시정지 상태로 만들지 않고 while문을 빠져나오는 방법



 무한 반복해서 출력하는 스레드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package sec06.exam06_stop;
 
public class PrintThread2 extends Thread {
    public void run() {    
        //how1 // 일부러 일시 정지를 시키는 방법
        /*try {
            while(true) {
                System.out.println("실행 중");
                Thread.sleep(1);
            }    
        } catch(InterruptedException e) {        
        }*/
        
        //how2 // 일시정지를 하지 않고 빠져나오는방법
        while(true) {
            System.out.println("실행 중");
            if(Thread.interrupted()) {
                break;
            }
        }
        
        System.out.println("자원 정리");
        System.out.println("실행 종료");
    }
}
c

 1초 후 출력 스레드를 중지시킴 (실행 클래스)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package sec06.exam06_stop;
 
public class InterruptExample {
    public static void main(String[] args)  {
        Thread thread = new PrintThread2();
        thread.start();
        
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
        }
        
        thread.interrupt();
    } //스레드를 종료시키기 위해 InterruptedException를 발생시킴
}
cs















Posted by 너래쟁이
: