실행 중인 하나의 애플리케이션
애플리케이션을 실행하면 운영체제로부터 실행에 필요한 메모리를 할당받아 애플리케이션의 코드를 실행하는데 이것이 프로세스이다. 하나의 프로세스는 다중 프로세스를 만들기도 한다.
ex) 크롬 브라우저를 두 개 실행했다면 두 개의 크롬 프로세스가 생성된 것
하나의 프로세스가 두 가지 이상의 작업을 처리하는 것이 멀티 스레드

멀티 프로세스들은 운영체제에서 할당받은 자신의 메모리를 가지고 실행하기 때문에 서로 독립적이다. 하나의 프로세스에서 오류가 발생해도 다른 프로세스에게 영향을 미치지 않는다. 하지만 멀티 스레드는 하나의 프로세스 내부에 생성되기 때문에 하나의 스레드가 예외를 발생시키면 프로세스 자체가 종료될 수 있어 다른 스레드에게 영향을 미치게 된다.
멀티 스레드 애플리케이션에서 실행중인 스레드가 하나라도 있다면, 프로세스는 종료되지 않는다.
메인 스레드는 반드시 존재하기 때문에 메인 작업 이외에 추가적인 병렬 작업의 수만큼 스레드를 생성한다. 자바에서는 작업 스레드도 객체로 생성되기 때문에 클래스가 필요하다.
java.lang.Thread 클래스를 직접 객체화해서 생성해도 되지만, Thread를 상속해서 하위 클래스를 만들어 생성할 수 도있다.
Runnable을 매개값으로 갖는 생성자를 호출해야 한다. Runnable은 작업 스레드가 실행할 수 있는 코드를 가지고 있는 객체라고 해서 붙여진 이름이다.
Thread thread = new Thread(Runnable target);
Runnable은 인터페이스 타입이기 때문에 구현 객체를 만들어 대입해야 한다. 구현 클래스는 run()을 재정의해서 작업 스레드가 실행할 코드를 작성해야 한다.
class Task implements Runnable {
public void run(){
스레드가 실행할 코드;
}
}
Runnable은 작업 내용을 가지고 있는 객체이지 실제 스레드는 아니다. Runnable 구현 객체를 생성한 후, 이것을 매개값으로 해서 Thread 생성자를 호출하면 작업 스레드가 생성된다.
Runnable task = new Task();
Thread thread = new Thread(task);
코드를 절약하기 위해 Thread 생성자를 호출할 때 Runnable 익명 객체를 매개값으로 사용할 수 있다.
Runnable 인터페이스는 run() 메소드 하나만 정의되어 있기 때문에 함수적 인터페이스이다.
람다식을 매개값으로 사용할 수도 있다.
Thread thread = new Thread( () -> {
스레드가 실행할 코드;
} )
start() 메소드를 호출해야 스레드가 실행된다.
thread.start();
start() 호출되면 매개값으로 받은 Runnable의 run() 메소드 실행한다.
Thread 클래스를 상속한 후 run 메소드를 재정의(overriding)해서 스레드가 실행할 코드를 작성하면 된다.
public clss WorkerThread extends Thread {
@Override
public void run(){
// 스레드가 실행할 코드
}
}
Thread thread = new WorkerThread();
코드를 절약해서 익명 객체로 생성할 수도 있다.
Thread thread = new Thread() {
public void run(){
스레드가 실행할 코드;
}
};
thread.start();
멀티 스레드 프로그램에서 스레드들이 객체를 공유해서 작업해야 하는 경우가 있다.
스레드 A를 사용하던 객체가 스레드 B에 의해 상태가 변경될 수 있기 때문에 스레드 A가 의도했던 것과는 다른 결과를 산출할 수도 있다.
스레드가 사용 중인 객체를 다른 스레드가 변경할 수 없도록 하려면 스레드 작업이 끝날 때까지 객체에 잠금을 건다.
멀티 스레드 프로그램에서 단 하나의 스레드만 실행할 수 있는 코드 영역을 임계 영역(critical section)이라고 한다. 자바는 임계 영역을 지정하기 위해 동기화(synchronized) 메소드와 동기화 블록을 제공한다.
스레드가 객체 내부의 동기화 메소드 또는 블록에 들어가면 객체에 잠금을 걸어 다른 스레드가 임계 영역 코드를 실행하지 못하도록 한다. 스레드가 동기화 메소드를 실행 종료하면 잠금이 풀린다.
동기화 블록의 내부 코드는 임계 영역이므로 한 번에 한 스레드만 실행할 수 있고 다른 스레드는 실행할 수 없다.
메소드 선언에 synchronized 키워드를 붙이면 된다. synchronized 키워드는 인스턴스와 정적 메소드 어디든 붙일 수 있다.
public synchronized void method(){
임계 영역; // 단 하나의 스레드만 실행
}
일부 내용만 임계 영역으로 만들고 싶다면 다음과 같이 동기화(synchronized) 블록을 만들면 된다.
public void method(){
// 여러 스레드가 실행 가능 영역
...
sychronized(공유객체){
임계 영역 // 단 하나의 스레드만 실행
}
// 여러 스레드가 실행 가능 영역
...
}
동기화 메소드와 동기화 블록이 여러 개 있을 경우, 스레드가 이들 중 하나를 실행할 때 다른 스레드는 해당 메소드는 물론이고 다른 동기화 메소드 및 블록도 실행할 수 없다. 하지만 일반 메소드는 실행이 가능하다.
같은 객체를 공유하지 않다면 다른 동기화 메스도와 블록 사용 가능
데몬 스레드는 주 스레드의 작업을 돕는 보조적인 역활을 수행하는 스레드이다. 주 스레드가 종료되면 데몬 스레드는 강제적으로 자동 종료된다.
ex) 워드 프로세서의 자동 저장, 가비지 컬렉터 등
스레드를 데몬으로 만들기 위해서는 주 스레드가 데몬이 될 스레드의 setDaemon(true)를 호출해주면 된다.
AutoSaveThread thread = new AutoSaveThread();
thread.setDaemon(true);
thread.start(); // setDaemon() 후에 star() 가능하다
isDaemon() : 데몬 스레드인지 구별하는 메소드
수업에서 배운 내용을 바탕으로 더 깊이 공부해 작성해보았다. 다만, 내가 참여한 프로젝트나 코딩테스트에서는 아직 스레드를 많이 활용하지 않아 실전 감각은 부족하다. 스레드 그룹과 스레드 풀에 대해서도 정리하고 싶었지만, 분량이 많아 다음 기회에 다뤄볼 예정이다.