관리 메뉴

나구리의 개발공부기록

자바의 정석 기초편 ch13 - 22 ~ 29 [sleep(), interrupt(), suspend(), resume(), stop(),join(),yield()] 본문

유튜브 공부/JAVA의 정석 기초편(유튜브)

자바의 정석 기초편 ch13 - 22 ~ 29 [sleep(), interrupt(), suspend(), resume(), stop(),join(),yield()]

소소한나구리 2023. 12. 18. 16:51

1) static void sleep()

  • 자기자신에게 동작하며 현재 쓰레드를 지정된 시간동안 멈추게 함
  • 예외처리를 필수로 해야하며 InterruptedException이 발생하면 깨어남
  • 특정 쓰레드를 지정해서 멈추게 하는 것은 불가능하며 자기자신의 쓰레드만 적용
// 코드 예시
static void sleep(long millis)	// 천분의 일초 단위 1000 = 1초
static void sleep(long millis, int nanos)	// 천분의 일초 + 나노초(10의 -9제곱)

// 예외처리 항상 필수
try {
	Thread.sleep(1, 500000);	// 쓰레드를 0.0015초 동안 멈추게 한다
} catch(InterruptedException e) {} 	// Exception의 자손 -> 예외 필수 처리

// 항상 예외처리를 하는 것이 불편하니 메서드를 만들어서 메서드 호출로 사용
void delay(long millis) {
	try {
	Thread.sleep(millis);
} catch(InterruptedException e) {}

---
delay(15);	// 메서드를 만들어서 호출하여 사용

// 특정 쓰레드 지정 불가(코드 오해)
try {
	th1.sleep(10000);	// 에러는 나지 않으나 th1이라는 쓰레드를 잠자기 하는걸로 오해 할 수 있음
} catch(InterruptedException e) {}

try {
	Thread.sleep(10000);	// 오해가 없도록 항상 이렇게 코드 작성 권장
} catch(InterruptedException e) {}

 

(1) 예제

  • 2초동안 멀티쓰레드가 동작하여 프로그램이 실행되고 종료 됨
class Ex13_8 {
	public static void main(String args[]) {
		runThread1 th1 = new runThread1();
		runThread2 th2 = new runThread2();
		th1.start();
		th2.start();

		delay(2*1000); // 2초 대기

		System.out.print("<<main 종료>>");
	} // main
    
	static void delay(long millis) {	// sleep을 메서드로 작성
		try {
			Thread.sleep(millis);	// 2초동안 잠자기, try - catch문 필수(예외처리 필수)
		} catch(InterruptedException e) {}
		
	}
}

class runThread1 extends Thread {
	public void run() {
		for(int i=0; i < 300; i++) System.out.print("-");
		System.out.print("<<th1 종료>>");
	} // run()
}

class runThread2 extends Thread {
	public void run() {
		for(int i=0; i < 300; i++) System.out.print("|");
		System.out.print("<<th2 종료>>");
	} // run()
}

2) interrupt()

  • 쓰레드를 깨워서 대기상태(WAITING)인 쓰레드를 실행대기 상태(RUNNABLE)로 만듦
  • sleep(), join(), wait()등의 메서드로 작업이 중단된 쓰레드를 interrupt()로 실행대기 상태로 전환함
  • 어떤 작업을 하다가 쓰레드의 작업을 중단시키고 싶을때, 중단 된 작업을 다시 작업시키고 싶을 때 사용
  • isInterrupted()로 쓰레드가 interrupted상태인지를 확인할 수 있음
interrupt()                     // 쓰레드의 interrupted상태를 false에서 true로 변경
boolean isInterrupted()         // 쓰레드의 interrupted상태를 반환 -> 상태 유지
static boolean interrupted()	// 현재 쓰레드의 interrupted상태를 알려줌 -> false로 변경

class Thread {  // Thread 클래스의 구성을 알기쉽게 재구성
	...
    boolean interrupted = false;	// interrupted의 기본상태는 false
    ...
	boolean isInterrupted() {       // 현재상태를 반환하는 isInterrupted()가 정의되어있음
    	return interrupted;
    }
    
	boolean interrupt() {	 // interrupted를 true로 변경하는 interrupt()가 정의되어있음
    	interrupted = true;
    }
}

 

(1) 예제

  • 카운트다운 실행 종료 -> 쓰레드 상태 확인 출력
  • interrupted()는 static 메서드이므로 자기 자신의 상태를 출력
  • 아래 예제 중 main 메서드안에서 interrupted()를 호출하면 th1의 상태를 확인 하는 것이 아니라 main메서드의 상태를 확인함 -> 헷갈리지 않도록 Thread.interrupted()로 호출
import javax.swing.JOptionPane;

class Ex13_9 {
    public static void main(String[] args) throws Exception {
        countThread th1 = new countThread();
        th1.start();

        // interrupt()호출 전 isInterrupted는 false
        System.out.println("isInterrupted():"+ th1.isInterrupted());

        String input = JOptionPane.showInputDialog("아무 값이나 입력하세요.");
        System.out.println("입력하신 값은 " + input + "입니다.");
        
        th1.interrupt();  // interrupt()를 호출하면, interrupted의 상태가 true가 됨
        
        // isInterrupted()로 상태를 계속 확인하면 상태는 true
        System.out.println("isInterrupted():"+ th1.isInterrupted()); // true
        System.out.println("isInterrupted():"+ th1.isInterrupted()); // true

        // 여기서 interrupted()를 호출하면 main쓰레드의 쓰레드 상태를 확인하는 것이 됨
        // main메서드는 interrupt()가 호출된 적이 없어서 false가 나옴
        // th1의 쓰레드의 상태 확인은 run()에서 interrupted()를하거나 th1.isInterrupted()로 확인해야함
// 		System.out.println("interrupted():"+ interrupted());        //false
// 		System.out.println("interrupted():"+ Thread.interrupted()); //false

    }
}

class countThread extends Thread {	// 카운트다운 메서드
    public void run() {
        int i = 10;

        // isInterrupted()가 false 이거나 0이 아닐 때 반복문을 실행
        while(i!=0 && !isInterrupted()) {
            System.out.println(i--);
            for(long x=0;x<2500000000L;x++); // 시간 지연
        }

        // isInterrupted()로 쓰레드의 상태를 확인 확인 -> 확인후에도 상태가 유지됨
        // this.을 제외해도 됨
        System.out.println("isInterrupted():"+this.isInterrupted()); // true
        System.out.println("isInterrupted():"+isInterrupted());      // true

        // interrupted()로 쓰레드의 상태를 확인 -> 확인 후에 상태를 false로 변경함
        // Thread.을 제외해도 됨
        System.out.println("isInterrupted():"+ interrupted());        // true
        System.out.println("isInterrupted():"+ Thread.interrupted()); // false 로 초기화

        // isInterrupted()가 true 이면 반복문이 바로 종료됨
        System.out.println("카운트가 종료되었습니다.");
    }
}

3) 정지, 일시정지, 재개

  • 아래의 쓰레드들은 deadlock(교착상태)를 일으키기가 쉽기 때문에 deprecated(사용을 권장하지 않음)되었음
  • 그래도 사용할 수는 있고, 직접 구현해서 사용도 가능함

(1) suspend()

  • 쓰레드를 일시정지

(2) resume()

  • 일시정지된 쓰레드를 재개

(3) stop()

  • 완전정지, 쓰레드를 즉시 종료
// 간단히 코드를 직접 구성하는 틀 예제, 실제로는 더 복잡함
class ThreadEx17_1 implements Runnable {
	boolean suspended = false;	// 일시정지인지 확인
	boolean stopped = false;	// 정지되었는지 확인
    
    public void run() {	// 멀티쓰레드 실행 메서드
    	while(!stopped) {	// stoppend = true이면
        	if(!suspended){	// suspended = true이면
            	/* 작업을 수행할 코드를 작성 */	코드를 수행
            }
        }
    }
    public void suspend() { suspended = true; }	// suspend()호출
    public void resume() { suspended = false; }	// resume()호출
    public void stop() { stop = true; }	// stop()호출
}

 

(4) 예제

class Ex13_10 {
    public static void main(String args[]) {
        MyThread th1 = new MyThread("*");
        MyThread th2 = new MyThread("**");
        MyThread th3 = new MyThread("***");
        th1.start();
        th2.start();
        th3.start();

        // deprecated 되었지만 직접 구현하면 deprecated표시가 사라짐
        try {
            Thread.sleep(2000);
            th1.suspend(); // 쓰레드 th1을 잠시 중단
            Thread.sleep(2000);
            th2.suspend(); // 쓰레드 th2를 잠시 중단
            Thread.sleep(3000);
            th1.resume(); // 쓰레드 th1이 다시 동작
            Thread.sleep(3000);
            th1.stop(); // 쓰레드 th1을 강제종료
            th2.stop(); // 쓰레드 th2을 강제종료
            Thread.sleep(2000);
            th3.stop(); // 쓰레드 th3을 강제종료
        } catch (InterruptedException e) {}
    } // main
}

// suspend,stop메서드를 직접 구현해보기
class MyThread implements Runnable {
    
    // volatile(쉽게 바뀌는 변수) - 자바의 정석 3판에서 자세히 설명
    volatile boolean suspended = false;	// volatile - 복사본 사용 금지(캐시메모리에 원본을 사용)
    volatile boolean stopped = false;	// 실행시 종료가 안될 경우 volatile를 붙히면 됨

    Thread th;

    MyThread(String name) {
        th = new Thread(this, name); // Thread(Runnable r, String name)
    }

    void start() {
        th. start();
    }
    void stop() {
        stopped = true;
    }
    void suspend() {
        suspended = true;
    }
    void resume() {
        suspended = false;
    }
    public void run() {
        while(!stopped) {
            if(!suspended) {
                System.out.println(Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch(InterruptedException e) {}
            }
        }
    } // run()
}

4) join()

  • 지정된 시간동안 특정 쓰레드가 작업하는 것을 기다림
  • 예외처리를 해야하며 InterruptedException이 발생하면 깨어남
void join()             // 작업이 모두 끝날때까지
void join(long millis)  // 천분의 일초 동안
void join(long millis, int nanos)  // 천분의 일초 + 나노초 동안

 

(1) 예제

  • 쓰레드1과 쓰레드2의 작업시간을 알기 위해 작업이 끝날 때까지 기다렸다가 실행
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 < 700; i++) {
			System.out.print(new String("-"));
		}
	} // run()
}

class ThreadEx11_2 extends Thread {
	public void run() {
		for(int i=0; i < 700; i++) {
			System.out.print(new String("|"));
		}
	} // run()
}

 

(2) 추가 예제

  • 가비지컬렉터를 흉내내는 예제 (가비지컬렉터는 데몬쓰레드 - 보조쓰레드)
  • 메모리부족이 발생하면 join()으로 gc메서드가 작업할 시간을 부여 후 정리가 되면 메모리를 사용하도록 동작
package ch13;

class GCMemoryManagementExample implements Runnable {
    private long usedMemory = 0;

    public void run() {
        while (true) {
            try {
                Thread.sleep(10 * 1000); // 10초를 기다림
            } catch (InterruptedException e) {
                System.out.println("Awaken by interrupt().");
            }

            System.gc(); // garbage collection를 수행
            System.out.println("Garbage Collected. Free Memory: " + freeMemory());

            for (int i = 0; i < 20; i++) {
                long requiredMemory = (long) (Math.random() * 10) * 20;
                
                // 힙의 메모리가 사용될 수 있는 양보다 작거나 전체 메모리의 60%이상 사용했을 경우 gc를 개시
                if (freeMemory() < requiredMemory || usedMemory() > totalMemory() * 0.4) {
                    System.gc(); // 작업 중인 스레드 gc를 개시

                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                    }
                }
                usedMemory += requiredMemory;
                System.out.println("usedMemory:" + usedMemory);
            }
        }
    }

    private long freeMemory() {
        return Runtime.getRuntime().freeMemory();
    }

    private long totalMemory() {
        return Runtime.getRuntime().totalMemory();
    }

    private long usedMemory() {
        return totalMemory() - freeMemory();
    }

    public static void main(String[] args) {
        Thread t = new Thread(new GCMemoryManagementExample());
        t.start();
    }
}

5) yield()

  • 남은 시간을 다음 쓰레드에게 양보하고 자신은 실행대기
  • yield()와 interrupt()를 적절히 사용하면 응답성과 효율을 높일 수 있음.
  • yield는 JVM스케줄러에게 통보(JVM스케줄러가 OS스케줄러에게 통보)하는 것이기 때문에 반드시 즉시 동작한다고 보장하기는 어려움

(1) 예제

package ch13;

public class MyThreadEx18 implements Runnable {
    private boolean suspended = false;
    private boolean stopped = false;
    private Thread th;

    public MyThreadEx18(String name) {
        th = new Thread(this, name);
    }

    public void run() {
        while (!stopped) {
            if (!suspended) {
                // 작업 수행
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {}
            } else {
                Thread.yield(); // suspended의 상태가 true이면 실행순서를 양보
            }
        }
    }

    public void start() {
        th.start();
    }

    public void resume() {
        suspended = false;
    }

    public void suspend() {
        suspended = true;
        th.interrupt();
    }

    public void stop() {
        stopped = true;
        th.interrupt();
    }
}

 

** 출처 : 남궁성의 정석코딩_자바의정석_기초편 유튜브