♨학습내용
☞ Thread 클래스와 Runnable 인터페이스
☞ 쓰레드의 상태
☞ 쓰레드의 우선순위
☞ Main 쓰레드
☞ 동기화
☞ 데드락
- 개념정리
- 프로세스(process)
- 사전적 의미로는 일의 과정이나 공정을 의미한다.
- 여기선 실행중인 프로그램을 의미한다.
- 프로그램을 실행하면 OS로부터 실행에 필요한 자원(메모리)을 할당받아 프로세스가 된다.
- 쓰레드(Thread)
- 프로세스라는 작업 공간내에서 실제로 작업을 처리하는 역할이다.
- 프로세스의 자원을 이용해서 작업을 수행한다.
- 모든 프로세스에는 최소한 하나 이상의 쓰레드가 존재한다.
- 쓰레드가 하나이면 싱글 쓰레드, 둘 이상이면 멀티 쓰레드 라고 한다.
- 멀티 태스킹(multi-tasking)
- 대부분의 OS가 지원한다.
- 여러 개의 프로세스가 동시에 실행 될 수 있는 것을 말한다.
- 멀티 쓰레딩(multi-threading)
- 하나의 프로세스 내에서 둘 이상의 쓰레드가 동시에 작업을 수행하는 것을 말한다.
- CPU의 코어가 한번에 하나의 작업만 수행할 수 있으므로, 실제로 동시에 처리되는 작업의 갯수와 일치한다.
- 코어가 아주 짧은 시간 동안 여러 작업을 번갈아 가며 수행함으로써, 여러 작업들이 모두 동시에 수행되는 것처럼 보이게한다.
- 프로세스의 성능은 쓰레드의 개수와 비례하지 않는다.
- 장점: CPU의 사용률 향샹, 자원을 보다 효율적으로 사용, 작업이 분리되어 코드가 간결해진다.
- 단점: 멀티쓰레딩은 여러 쓰레드가 같은 프로세스 내에서 자원을 공유하면서 작업을 하기 때문에, 발생할 수 있는 동기화(synchronization), 교착상태(deadlock)와 같은 문제들을 고려해서 신중히 프로그래밍 해야한다.
- Thread 클래스와 Runnable 인터페이스
- 쓰레드를 구현하는 방법은 2가지가 있다.
- Thread클래스를 상속받는 방법과 Runnable 인터페이스를 구현하는 방법
- Thread 클래스
- Thread 레퍼런스 객체를 생성하여 run() 함수(Method)를 오버라이드 해주는 방법이다.
public class Test extends Thread {
@Override
public void run() {
System.out.println("Thread run");
}
public static void main(String[] args) {
Test test = new Test();
test.start();
}
}
- 첫번째 방법으로는 위 예제 코드처럼 Thread 클래스를 상속받은 클래스를 작성하는 방법이 있다.
- Thread 클래스의 run() 메서드는 새 쓰레드를 만들며, Thread를 상속받은 클래스에서는 이 run() 메서드를 오버라이딩해서 사용해야 한다.
- run메서드는 단순 return하도록 작성되어 있기 때문에, 오버라이딩 하지 않으면 쓰레드가 바로 종료된다.
- 그런데, 예제 코드에서 보면 Test 객체 test를 만들고는 test의 run이 아니라 start() 메서드를 호출한 것을 볼 수 있다.
- 만약 run 메서드를 호출하게 되면 쓰레드가 생성만 되고 Runnable한 상태가 되지 않기 때문에, 정상적으로 실행되지 않는다.
- 그래서 start 메서드를 통해 실행시켜줘야 한다.
- start()는 Thread 클래스에 구현된 메서드이며, 오버라이딩 해서는 안된다.
- start()는 생성된 쓰레드 객체를 Runnable하게 전환시킨 후 JVM에 의해 이 쓰레드가 선택되면 run 메서드가 호출되고 실행된다.
- Runnable 인터페이스
- Runnable 인터페이스를 구현해서 만드는 구현체이다.
public class Test {
public static void main(String[] args) {
Thread thread = new Thread(new Foo());
thread.start();
}
}
class Foo implements Runnable {
@Override
public void run() {
System.out.println("Thread run");
}
}
- Runnable 인터페이스를 구현해서 만드는 구현체에는 start() 메서드가 존재하지 않는다.
- 그래서 별도의 Thread 객체를 생성하고, 이 때 Runnable 인터페이스의 구현체를 인자로 넘겨주어야 한다.
- Runnable 인터페이스의 구현체로 만드는 쓰레드도, Thread 클래스를 상속해서 만드는 쓰레드와 마찬가지로 start()를 통해 실행시킨다.
- 쓰레드의 상태
- 쓰레드는 총 6가지의 상태를 가지며 이 상태들은 JVM에 의해 관리된다.
상태(State) | 설명(desciprtion) |
NEW | 쓰레드 객체는 생성되었지만, 실행되지 않은 상태, start()를 호출해주어야 Runnable 상태가 된다. |
RUNNABLE | 쓰레드가 실행되고 있거나 실행 준비되어 JVM의 스케줄링을 기다리는 상태이다. |
BLOCKED | 쓰레드가 실행 중지 상태이며, 모니터락(Moniter Lock)이 풀리기를 기다리는 상태이다. |
WAITING | 쓰레드가 대기중인 상태이다. |
TIMED_WAITING | 특정 시간만큼 쓰레드가 대기중인 상태이다. |
TERMINATED | 쓰레드가 종료된 상태로, 한 번 TERMINATED 상태로 돌입한 쓰레드는 다른 상태가 될 수 없다. |
- 쓰레드의 우선순위
- 자바에서 Thread(쓰레드)는 Priority(우선순위)에 관한 필드를 가지고 있다.
- 쓰레드가 수행하는 작업의 중요도에 따라 쓰레드에 우선순위를 부여하여 쓰레드 별로 작업시간을 다르게 부여할 수 있다.
- 우선순위가 높은 쓰레드 일수록 더 많은 작업시간을 할당받는다.
- 쓰레드의 우선순위는 절대적인 값이 아니라 상대적인 것이며, 쓰레드 클래스 내부에 멤버 변수로 존재한다.
필드 | 설명 |
static int MAX_PRIORITY (10) | 쓰레드가 가질 수 있는 최대 우선순위를 명시한다. |
static int MIN_PRIORITY (1) | 쓰레드가 가질 수 있는 최소 우선순위를 명시한다. |
static int NORMAL_PRIORITY (5) | 쓰레드가 생성될 때 가지는 기본 우선순위를 명시한다. |
- 쓰레드가 우선순위로 보통 값(NORMAL_PRIORITY) 5를 가지고 있기 때문에 main 쓰레드의 모든 자식 쓰레드들은 5의 우선순위를 가지고 생성된다.
- 쓰레드의 setPriority(int newPriority) 메서드를 사용하면 우선순위를 newPriority 값으로 바꿀 수 있다.
- Main 쓰레드
- 모든 자바 애플리케이션은 main thread가 main() 메서드를 실행하면서 시작한다.
- 메인 쓰레드는 main() 메서드의 첫 코드부터 아래로 순차적으로 실행하고, main() 메서드의 마지막 코드를 실행하거나 return문을 만나면 종료된다.
- 메인 쓰레드는 필요에 따라 작업 쓰레드를 만들어서 병렬로 코드를 실행한다.
- 메인쓰레드가 실행되면 그 안에서 멀티쓰레드 구현이 가능해진다.
- 여기서 주의할 점은 싱글쓰레드의 경우, 메인쓰레드가 종료되면 프로세스가 모두 종료되지만,
- 멀티쓰레드의 경우 메인쓰레드가 종료되어도 프로세스는 종료되지 않는다는 점을 주의해야한다.
- 동기화
- 멀티쓰레드 프로세스의 경우, 여러 쓰레드가 같은 프로세스 내의 자원을 공유해서 작업하는 것을 말한다.
- 한 쓰레드가 진행중인 작업을 다른 쓰레드가 간섭하지 못하도록 막는 것을 말한다.
- 즉, 현재 실행중인 쓰레드를 제외하고, 나머지 쓰레드에서는 데이터에 접근할 수 없도록 막는 개념이다.
- 멀티쓰레드를 잘 사용하면 성능을 증가시키지만, 공유자원에 대한 동기화가 없는 경우에는 데이터의 안정성과 신뢰성을 보장할 수 없다.
- synchronized를 이용한 동기화
- synchronized가 호출된 시점부터 해당 메서드가 포함된 객체의 lock을 얻어서 작업을 수행하다가 메서드가 종료되면 lock을 반환한다.
- lock을 걸고자 하는 객체를 참조변수로!
- 임계영역(critical section)은 멀티쓰레드 프로그램의 성능을 좌우하기 때문에, 가능하면 임계영역을 최소화 해서 효율적으로 돌아가도록 해야한다.
// 1. 메서드 전체를 임계 영역(critical section)으로 지정
public synchronized void calcSum() {
//...
}
// 2. 특정한 영역을 임계 영역으로 지정
synchronized (객체의 참조변수) {
//...
}
class ThreadEx {
public static void main(String args[]) {
Runnable r = new RunnableEx();
new Thread(r).start();
new Thread(r).start();
}
}
class Account {
private int balance = 1000; // private으로 해야 동기화가 의미가 있다.
public int getBalance() {
return balance;
}
public synchronized void withdraw(int money){ // synchronized로 메서드를 동기화한다.
if(balance >= money) { // 이 구분을 통과하고 출금하기 직전에 다른 쓰레드가 끼어들면 잔고가 마이너스가 된다.
try { Thread.sleep(1000);} catch(InterruptedException e) {}
balance -= money;
}
}
}
class RunnableEx implements Runnable {
Account account = new Account();
public void run() {
while(account.getBalance() > 0) {
// 100, 200, 300중의 한 값을 임으로 선택해서 출금(withdraw)
int money = (int)(Math.random() * 3 + 1) * 100;
account.withdraw(money);
System.out.println("balance:" + account.getBalance());
}
}
}
- 아래처럼 설정도 가능하다.
public void withdraw(int money){
synchronized(this){
if(balance >= money) {
try { Thread.sleep(1000);} catch(InterruptedException e) {}
balance -= money;
}
}
}
- wait()와 notify()
- 임계영역(critial section)의 코드를 더이상 진행할 수 없으면, wait() 호출하여 쓰레드가 lock을 반납하고 기다리게 한다.
- lock이 반납되면 다른 쓰레드가 lock을 얻어 해당 객체에 대한 작업을 진행 할 수 있다.
- notify()를 호출해서 작업을 중단했던 쓰레드가 다시 lock을 얻어서 진행한다. (재진입, reentrance)
- 그렇지만, 오래 기다린 쓰레드가 lock을 얻는다는 보장이 없다.
- 이러한 현상을 기아현상(starvation) 이라고 하는데, notifyAll()을 사용해서 해결 가능하다.
- race condition : 여러 쓰래드가 lock을 얻기 위해 서로 경쟁하는 것이다.
- 데드락
- 2개 이상의 프로세스가 다른 프로세스의 작업이 끝나기만 기다리며 작업을 더 이상 진행하지 못하는 상태를 교착 상태 (dead lock)이라고 한다.
- 교착 상태가 발생하기 위해서는 아래의 4가지 조건을 만족해야 한다. 이 4가지 조건을 교착 상태의 필요조건 이라고 한다.
- 상호 배제 (Mutual exclusion)
- 자원을 공유하지 못하면 교착 상태가 발생한다. 여기서 자원은 배타적인 자원이어야 한다. 배타적인 자원은 임계구역에서 보호되기 때문에 다른 쓰레드가 동시에 사용할 수 없다. - 비선점 (No preemption)
- 자원을 빼앗을 수 없으면 자원을 놓을 때까지 기다려야 하므로 교착상태가 발생한다. - 점유와 대기 (Hold and wait)
- 자원 하나를 잡은 상태에서 다른 자원을 기다리면 교착 상태가 발생한다. - 원형 대기 (Circular wait)
- 자원을 요구하는 방향을 원을 이루면 양보를 하지 않기 때문에 교착상태가 발생한다.
- 위 조건들 중 한가지라도 만족하지 않으면 교착상태는 일어나지 않는다.
'Study > Java_study' 카테고리의 다른 글
Java_study_12 (애노테이션) (0) | 2021.09.14 |
---|---|
Java_study_11 (Enum) (0) | 2021.09.07 |
Java_study_9 (예외처리) (0) | 2021.08.23 |
Java_study_8 (인터페이스) (0) | 2021.08.17 |
Java_study_7 (패키지) (0) | 2021.08.11 |