▸JAVA/기본 문법

Thread (스레드)_스레드 제어 [2/3]

코데방 2020. 1. 30.
728x90

[ wait(), notify(), notifyAll() ]

  • 스레드를 조금 더 세밀하게 제어할 수 있도록 해주는 메소드
  • 동기화(Synchronized) 처리가 된 블록 안에만 사용이 가능
  • wait() : 스레드 상태를 RUNNABLE → WAITING 상태로 변경
  • notify() : WAITING 상태의 특정 스레드를 RUNNABLE 상태로 변경
  • notifyAll() : WAITING 상태의 모든 스레드를 RUNNABLE 상태로 변경

먼저 스레드의 상태 변경을 통해 원하는 조건에 스레드가 중지됐다 다시 가동하도록 하는 방법입니다. 아래 코드에서는 배열에 값이 하나도 없으면 Thread-1을 WAITING 상태로 만들었다가, 다른 스레드에서 배열에 값을 넣으면 notify() 메소드를 통해 Thread-1이 다시 RUNNABLE 상태로 변경되어 작동되도록 하는 코드입니다.

 

세 메소드 모두 Object 클래스에 있는 가장 기본 메소드이며 현재 실행되고 있는 스레드에 적용됩니다. 특정 메소드 안에 해당 메소드를 추가한다고 하면, 그 메소드가 실행되고 있는 현재 스레드에 적용이 됩니다. 또한 Synchronized로 동기화 처리가 된 블록 안에서만 사용 가능한 점에 유의해야 합니다. 

 

아래 예시 코드에서 보면 처음에 배열의 갯수가 0개이기 때문에 WAITING 상태가 되었다가 다른 스레드에서 notifyALL()을 해주자 살아나서 BLOCKED 상태로 대기하고 있습니다.  BLOCKED은 대기 상태로, 하나의 객체를 여러 스레드가 사용하고 있어 한 스레드가 객체를 점유하고 있을 때는 다른 스레드가 점유할 수 없는 상태를 의미합니다.

 

기본적으로 하나의 객체를 여러 스레드에서 사용할 때는 먼저 점유한 쪽 외에는 모두 BLOCKED 상태가 됩니다. 그럼에도 이전글에서 다룬 동기화의 문제가 발생하는 이유는 여러 스레드가 무작위 순서로 객체에 접근하기 때문입니다. 각 스레드들이 단 하나의 로직이 아닌 여러 개의 순차적인 로직들을 가지고 있는데 각 로직의 순서가 엉망이 되버린다는 거죠. 그래서 synchronized로 블록 지정을 해두면 해당 블록의 로직을 한 스레드가 순서대로 모두 처리한 뒤 다른 스레드에게 넘겨주게 되어 내부적으로 로직 순서가 엉키는 문제를 방지할 수 있게 됩니다.

 

package hs;

import java.util.ArrayList;

public class Main {

	public static void main(String[] args) {

		M1 m1 = new M1();
		Thread del = new Thread(new ThreadDel(m1));
		Thread add = new Thread(new ThreadAdd(m1, del));

		del.start();
		add.start();

	}
}

// 생산 관련 객체
class M1 {

	ArrayList<Integer> arr = new ArrayList<Integer>();

	// 추가 메소드
	synchronized void add(int n, Thread t) {

		arr.add(n);
		System.out.println(n + " 추가 완료(다른스레드 : " + t.getState() + ")");
		notify();
		try {
			Thread.sleep(300);
		} catch (Exception e) {
			System.out.println("Error");
		}
	}

	// 삭제 메소드
	synchronized void del() {

		// 배열 값 없으면 스레드 중지
		if (arr.size() == 0) {
			try {
				wait();
			} catch (Exception e) {
				System.out.println("Error");
			}
		}

		// notify 되고 나면 스레드 재가동
		int n = arr.get(0);
		arr.remove(0);
		System.out.println(n + " 삭제 완료(현재상태 : " + Thread.currentThread().getState() + ")");
	}
}

class ThreadAdd implements Runnable {

	M1 m1;
	Thread t;

	ThreadAdd(M1 m1, Thread t) {
		this.m1 = m1;
		this.t = t;
	}

	@Override
	public void run() {

		for (int i = 0; i < 20; i++) {
			m1.add(i, t);
		}
	}

}

class ThreadDel implements Runnable {

	M1 m1;

	ThreadDel(M1 m1) {

		this.m1 = m1;
	}

	@Override
	public void run() {
		for (int i = 0; i < 20; i++) {
			m1.del();
		}
	}
}

 

 


 

 

[ join() ]

  • 스레드 간 종속 관계를 형성함
  • 갑 스레드 작업이 완료된 후 을 스레드가 동작하도록 함

위의 wait()와 notify()가 특정 조건에 따라 스레드를 제어하는 것이라면, join() 메소드는 스레드 간에 종속 관계를 만들어줍니다. 갑 스레드가 일을 끝내야 을 스레드가 일을 시작할 수 있는 구조입니다. 아래와 같이 사용하면 됩니다.

 

package hs;

public class Main {

	public static void main(String[] args) {

		T1 t1 = new T1("갑 스레드1");
		T1 t11 = new T1("갑 스레드2-");
		T2 t2 = new T2(t1, t11);
		
		t1.start();
		t11.start();
		t2.start();
	}
}


class T1 extends Thread {
	
	String a;
	
	T1(String a) {
		this.a = a;
	}
	
	@Override
	public void run() {
		for(int i = 0; i < 5; i++) {
			
			System.out.println(a + " " + i);
			
		}
	}
}

class T2 extends Thread {
	
	Thread t, v;
	
	T2(Thread t, Thread v) {
		this.t = t;
		this.v = v;
	}
	
	@Override
	public void run() {
		
		try {
			t.join();
			v.join();
		} catch(InterruptedException e) {
			System.out.println("Error");
		}
		
		for(int i = 0; i < 5; i++) {
			System.out.println("을 스레드 --- " + i);
		}
	}
}

 

 


 

 

[ sleep() ]

  • 스레드를 일정 시간 동안 멈춰둠 (TIME_WAITING 상태로 전환)

이 메소드는 워낙 유명한 메소드라 예시 없이 넘어가겠습니다. 매개변수로 1/1000초를 주면 해당 시간 동안 스레드가 중지됐다가 다시 작동합니다. 

 


 

[ interrupt(), intterupted(), isInterrupted() ]

  • sleep(), wait(), join() 메소드가 실행되어 중지(실행 대기 상태)되어 있는 스레드의 실행을 중지함
  • 대기 상태가 아닌 경우 interrupt() 발생 여부를 확인하여 종료시킴

스레드의 run() 메소드가 완료되지 않은 시점에 스레드를 종료시키는 메소드입니다. 해당 스레드가 대기 상태로 들어가면 InterruptedException 예외를 발생시킵니다. 위 세 가지 메소드를 예외 처리해야하는 이유가 바로 이것 때문입니다. 스레드에서 예외가 발생되면 catch 구문에서 받아 종료를 처리합니다. 

 

주의할 점은 interrupt() 메소드 실행 즉시 예외를 발생시키고 중단되는 것이 아니라 해당 스레드가 대기 상태에 들어가야 실제로 예외가 발생한다는 점입니다. 즉 스레드가 대기 상태에 들어가지 않고 계속 작동한다면 중단될 일이 없습니다. 이 경우에는 해당 스레드에서 interrupt()가 발생했는지 계속 체크해주는 것으로 해결할 수 있습니다.

 

run() 메소드 내에서 체크해주기 위해 Thread 클래스의 static 메소드인 interrupted() 메소드 또는 인스턴스 메소드인 isInterrupted() 메소드를 사용하면 됩니다. 만약 해당 스레드에서 중단이 발생됐다면 true를 반환하기 때문에 아래와 같이 사용할 수 있습니다. 

 

package hs;

import javax.swing.text.html.HTMLDocument.HTMLReader.IsindexAction;

public class Main {

	public static void main(String[] args) {

		T1 t1 = new T1();
		t1.start();
		try {
			Thread.sleep(1);
		} catch (InterruptedException e) {
			System.out.println("");
		}
		t1.interrupt();
		System.out.println("중단되었습니다.");
	}
}

class T1 extends Thread {

	@Override
	public void run() {

		// interrupt가 발생하지 않은 동안만 반복
		while (!isInterrupted()) {
			// 또는 !Thread.interrupted();
	
			System.out.println("중단 전..! ");
		}
	}
}
728x90

댓글

💲 추천 글