프로세스는 데이터, 컴퓨터 자원, 그리고 스레드로 구성되는데, 스레드는 데이터와 애플리케이션이 확보한 자원을 활용하여 소스 코드를 실행한다. 즉, 스레드는 하나의 코드 실행 흐름이라고 볼 수 있다.
작업 스레드를 활용한다는 것은, 다시 말해 작업 스레드가 수행할 코드를 작성하고, 작업 스레드를 생성하여 실행시키는 것을 의미한다. 스레드가 수행할 코드를 클래스 내부에 작성해야 하며, run()
메서드 내에 스레드가 처리할 작업을 작성하도록 규정되어 있다.
스레드의 이름 조회하기
스레드의 이름은 스레드의_참조값.getName()
으로 조회할 수 있다.
스레드의 이름 설정하기
스레드의 이름은 스레드의_참조값.setName()
으로 설정할 수 있다.
스레드 인스턴스의 주소값 얻기
실행 중인 스레드의 주소값을 사용해야 하는 상황이 발생한다면 Thread
클래스의 정적 메서드인 currentThread()
를 사용하면 된다. Thread.currentThread()
싱글 스레드 프로세스는 데이터에 단 하나의 스레드만 접근하므로, 문제될 사항이 없다. 그러나, 멀티 스레드 프로세스의 경우, 두 스레드가 동일한 데이터를 공유하게 되어 문제가 발생할 수 있다.
try { Thread.sleep(1000); } catch (Exception error) {}
Thread.sleep(1000);
스레드를 일시 정지시키는 메서드다. 어떤 스레드가 일시 정지되면, 대기열에서 기다리고 있던 다른 스레드가 실행된다.
Thread.sleep()
은 반드시 try … catch
문의 try
블럭 내에 작성해주어야 한다.
try { … } catch ( ~ ) { … }
try … catch
문은 예외 처리에 사용되는 문법이다.
try
의 블록 내의 코드를 실행하다가 예외 또는 에러가 발생하면 catch
문의 블럭에 해당하는 내용을 실행하라는 의미가 된다.
임계 영역(Critical section)은 오로지 하나의 스레드만 코드를 실행할 수 있는 코드 영역을 의미하며, 락(Lock)은 임계 영역을 포함하고 있는 객체에 접근할 수 있는 권한을 의미한다.
특정 코드 구간을 임계 영역으로 설정할 때에는 synchronized
라는 키워드를 사용한다. synchronized
키워드는 두 가지 방법으로 사용할 수 있다.
synchronized
키워드를 작성하면 메서드 전체를 임계 영역으로 설정할 수 있다. 이렇게 메서드 전체를 임계 영역으로 지정하면 메서드가 호출되었을 때, 메서드를 실행할 스레드는 메서드가 포함된 객체의 락을 얻게 된다.class Account {
...
public synchronized boolean withdraw(int money) {
if (balance >= money) {
try { Thread.sleep(1000); } catch (Exception error) {}
balance -= money;
return true;
}
return false;
}
}
withdraw()
가 호출되면, ithdraw()
를 실행하는 스레드는 withdraw()
가 포함된 객체의 락을 얻으며, 해당 스레드가 락을 반납하기 이전에 다른 스레드는 해당 메서드의 코드를 실행하지 못하게 된다.synchronized
키워드와 함께 소괄호()
안에 해당 영역이 포함된 객체의 참조를 넣고, 중괄호{}
로 블럭을 열어, 블럭 내에 코드를 작성하면 된다.class Account {
...
public boolean withdraw(int money) {
synchronized (this) {
if (balance >= money) {
try { Thread.sleep(1000); } catch (Exception error) {}
balance -= money;
return true;
}
return false;
}
}
}
this
에 해당하는 객체의 락을 얻고, 배타적으로 임계 영역 내의 코드를 실행한다.start()
는 스레드를 실행 대기 상태로 만들어준다. 즉, 스레드에는 상태라는 것이 존재하며 스레드의 상태를 바꿀 수 있는 메서드가 존재한다.
스레드 실행 제어 메서드
1. sleep(long milliSecond) : milliSecond 동안 스레드를 잠시 멈춘다.
static void sleep(long milliSecond)
sleep()
은 Thread
의 클래스 메서드입니다. 따라서 sleep()
을 호출할 때에는 Thread.sleep(1000);
과 같이 클래스를 통해서 호출하는 것이 권장된다.
sleep()
을 호출하면 sleep()
을 호출하는 코드를 실행한 스레드의 상태가 실행 상태
에서 일시 정지(TIMED_WAITING)
상태로 전환된다.
sleep()
에 의해 일시 정지
된 스레드는 다음의 경우에 실행 대기
상태로 복귀한다.
인자로 전달한 시간 만큼의 시간이 경과한 경우
interrupt()를 호출한 경우
interrupt()
를 호출하여 스레드를 실행 대기 상태로 복귀시키고자 한다면 반드시 try … catch
문을 사용해서 예외 처리를 해주어야 한다. interrupt()
가 호출되면 기본적으로 예외가 발생하기 때문이다.
따라서 sleep()
을 사용할 때에는 아래처럼 try … catch
문으로 sleep()
을 감싸주어야 한다.
try { Thread.sleep(1000); } catch (Exception error) {}
2.interrupt() : 일시 중지 상태인 스레드를 실행 대기 상태로 복귀시킨다.
void interrupt()
interrupt()
는 sleep()
, wait()
, join()
에 의해 일시 정지 상태에 있는 스레드들을 실행 대기 상태로 복귀시킨다.sleep()
, wait()
, join()
에 의해 일시 정지된 스레드들의 코드 흐름은 각각 sleep()
, wait()
, join()
에 멈춰있다.
멈춰있는 스레드가 아닌 다른 스레드에서 멈춰 있는 스레드.interrupt()
를 호출하면, 기존에 호출되어 스레드를 멈추게 했던 sleep()
, wait()
, join()
메서드에서 예외가 발생되며, 그에 따라 일시 정지가 풀리게 된다.
3. yield() : 다른 스레드에게 실행을 양보합니다.
static void yield()
yield()
를 호출하면 example의 값이 false
일 때에 무의미한 while
문의 반복을 멈추고 실행 대기 상태로 바뀌며, 자신에게 남은 실행 시간을 실행 대기열 상 우선순위가 높은 다른 스레드에게 양보한다.public void run() {
while (true) {
if (example) {
...
}
else Thread.yield();
}
}
4. join() : 다른 스레드의 작업이 끝날 때까지 기다립니다.
void join()
void join(long milliSecond)
join()
은 다음과 같은 측면에서 sleep()
과 유사하다.
join()
을 호출한 스레드는 일시 중지 상태가 된다.
try … catch
문으로 감싸서 사용해야 한다.
interrupt()
에 의해 실행 대기 상태로 복귀할 수 있다.
sleep()
과 join()
은 다음과 같은 차이점을 가진다.
sleep()
은 Thread 클래스의 static
메서드이다. 반면, join()
은 특정 스레드에 대해 동작하는 인스턴스 메서드
이다.예 : Thread.sleep(1000);
예 : thread1.join();
5. wait(), notify() : 스레드 간 협업에 사용된다.
스레드를 활용하다보면, 두 스레드가 교대로 작업을 처리해야할 때가 있다. 이 때 사용할 수 있는 상태 제어 메서드가 wait()
과 notify()
이다.
먼저, 스레드A가 공유 객체에 자신의 작업을 완료한다. 이 때, 스레드B와 교대하기 위해 notify()
를 호출한다. notify()
가 호출되면 스레드B가 실행 대기
상태가 되며, 곧 실행된다. 이어서 스레드A는 wait()
을 호출하며 자기 자신을 일시 정지
상태로 만든다.
이후 스레드B가 작업을 완료하면 notify()
를 호출하여 작업을 중단하고 있던 스레드A를 다시 실행 대기
상태로 복귀시킨 후, wait()
을 호출하여 자기 자신의 상태를 일시 정지
상태로 전환한다.