Obj. 자바의 멀티쓰레드 프로그래밍에 대해 학습.
Todo.
10-1. Thread 클래스와 Runnable 인터페이스
10-2. 쓰레드의 상태
10-3. 쓰레드의 우선순위
10-4. Main 쓰레드
10-5. 동기화
10-6. 데드락
10-0. Threa vs Process
프로세스와 스레드의 차이
프로세스란 ? 단순히 실행중인 프로그램(program)이라고 할 수 있다.
즉, 사용자가 작성한 프로그램이 운영체제에 의해 메모리 공간을 할당받아 실행 중 인것을 말한다.
이러한 프로세스는 프로그램에 사용되는 데이터와 메모리 등의 자원 그리고 스레드로 구성이 된다.
쓰레드란? 프로세스 내에서 실제로 작업을 수행하는 주체를 의미한다. 모든 프로세스에는 한 개 이상의 쓰레드가 존재하여 작업을 수행한다. 또한, 두개 이상의 쓰레드를 가지는 프로세스를 멀티쓰레드 프로세스 라고 한다.
10-1. Thread 클래스와 Runnable 인터페이스
- 쓰레드를 생성하는 방법은 크게 두 가지 방법이 있다.
1. Thread 클래스를 상속
2. Runnable 인터페이스를 구현
Thread 클래스 또한 Runnable 인터페이스를 구현 한 클래스이므로 어느 것이던 작업하고 싶은 내용을 run() 메소드에 작성을 하면 됨.
public static void runBasic() {
Runnable runnable = new RunnableSample();
new Thread(runnable).start();
ThreadSample threadSample = new ThreadSample();
threadSample.start();
System.out.println("finish");
}
public class RunnableSample implements Runnable {
@Override
public void run() {
System.out.println("this is RunnableSample's run() method");
}
}
public class ThreadSample extends Thread {
@Override
public void run() {
System.out.println("this is ThreadSample's run() method.");
}
}
여기서 메인에서 start()를 하는 것을 볼 수 있는데, run()을 하면 단지 메소드 호출의 의미를 가지므로 start()를 해야 호출 스택을 가지게 된다.
언제 쓰는지 어떻게 구분 할까?
- extends Thread, 즉 쓰레드를 상속받아 사용할 때 run()외에도 다른 것들을 Override를 해야할 필요가 있으면 Thread를 상속해서 만든다. run()만 사용해도 되는 경우에는 Runnable을 사용하면 된다. 또는 Thread를 상속받을 클래스가 다른 클래스도 상속받아야 된다면 Runnable을 사용한다.
10-2. 쓰레드의 상태
쓰레드의 상태는 크게 6가지로 구성된다.
NEW |
쓰레드의 객체는 생성되었지만, 아직 시작되지 않은 상태 |
RUNNABLE |
쓰레드가 실행 중인 상태 |
BLOCKED |
쓰레드가 실행 중지 상태이며, 모니터 락(monitor lock)이 풀리기를 기다리는 상태 |
WAITING |
쓰레드가 대기 중인 상태 |
TIME_WAITING |
특정 시간만큼 쓰레드가 대기중인 상태 |
TERMINATED |
쓰레드가 종료된 상태 |
쓰레드의 상태와 스케줄링에 관련된 메소드
1. sleep() - 주어진 시간 만큼 대기를 하게 된다. 또한 이를 사용할 때는 try-catch로 묵어 줘야한다. InterruptedExcepotion을 던질 수도 있기 떄문이다.
2. join() - 다른 쓰레드의 작업을 기다린다. 작업 중간에 다른 쓰레드의 작업을 참여 시킨다는 의미이므로 일정 시간 동안 작업을 수행하도록 한다. 별도의 파라메터가 없으면 쓰레드가 작업을 모두 마칠 때 까지 기다린다.
3. interrupted() - 쓰레드의 작업을 취소한다. 쓰레드에게 작업을 멈추라고 요청한다. 단지 요청을 할 뿐이지 쓰레드를 강제로 종료시키지는 못한다. 이는 인스턴스 변수를 바꾼다.
4. yield() - 다른 쓰레드 에게 양보한다.
자신에게 주어진 실행 시간을 다른 차례의 쓰레드에게 양보한다. 그리고 자신은 실행 대기 상태가 된다.
5. stop(), suspend(), resume() - 쓰레드를 데드락으로 만들기 쉽기 때문에 deprecated 되었음.
10-3. 쓰레드의 우선순위
다중 작업을 진행할 때는 멀티쓰레드 방식을 사용하는 데 이 경우는 크게 동시성(Concurrency), 병렬성(Parallelism)으로 나뉜다.
동시성은 싱글코어에 멀티 쓰레드를 번갈아 가면서 실행한다. 병렬성은 각 코어가 각 쓰레드를 실행한다.
각 쓰레드는 우선순위에 대한 필드를 가지고 있다.
static int MAX_PRIORITY(최대 우선순위), static int MIN_PRIORITY(최소 우선순위), static int NORM_PRIORITY(기본 우선순위)가 있다. getPriority(), setPriority() 메소드를 통해서 쓰레드의 우선순위를 반환하거나 변경할 수 있다.
하지만, 이 우선순위는 절대적인 것이 아니다. -> 스케줄링 방식에 따라 지켜지지 않을 수 있다. 우선 순위로 많은 실행을 할 뿐이다.
10-4. Main 쓰레드
자바 실행 환경인 JVM은 하나의 프로세스 이며, main()메소드가 main 쓰레드이다. 하나의 쓰레드만 사용 하는 어플리케이션을 싱글 쓰레드 어플리케이션이고, 여러개의 쓰레드가 돌아가면 멀티쓰레드가 되는 것이다. 이때 main 쓰레드가 종료되어도 멀티 쓰레드 환경에서는 프로게스가 종료되지 않는다.
Daemon 쓰레드 : main 쓰레드의 작업을 돋는 것이며 main 쓰레드가 종료되면 데몬 쓰레드는 강제적으로 종료됨. setDaemon(boolean)을 사용하여 설정한다.
10-5. 동기화
멀티 쓰레드의 경우에 자원을 공유 하게 되고 이를 critical section(임계 영역)이라고 한다. 여러 개의 쓰레드에서 한 개의 리소스를 사용하려고 할 때 사용하려는 쓰레드를 제외한 나머지들을 접근하지 못하게 막는 것이다.이를 Tread-Safe라고 한다. 자바에서는 Syncronized 를 사용해 왔는데 추가로 Atomic 클래스나 Volatile 키워드를 사용 할 수 있다.
Synchronized :
자바의 예약어 중 하나로, 메소드 자체를 synchronized로 선언 하는 방법이 있고, 다른 하나는 메소드 내의 특정 블록만 Synchronized로 감싸는 방법이 있다.
class Table {
String[] dishNames = {"donut", "donut", "burger"};
final int MAX_FOOD = 6;
private ArrayList<String> dishes = new ArrayList<>();
public synchronized void add(String dish) {
while (dishes.size() >= MAX_FOOD) {
String name = Thread.currentThread().getName();
System.out.println(name + " is waiting.");
try {
wait();
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
dishes.add(dish);
notify();
System.out.println("Dishes:" + dishes.toString());
}
public void remove(String dishName) {
synchronized (this) {
String name = Thread.currentThread().getName();
while (dishes.size() == 0) {
System.out.println(name + " is waiting.");
try {
wait();
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
while (true) {
for (int i = 0; i < dishes.size(); i++) {
if (dishName.equals(dishes.get(i))) {
dishes.remove(i);
notify();
return;
}
}
try {
System.out.println(name + " is waiting.");
wait();
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
}
}
public int dishNum() {
return dishNames.length;
}
}
10-6. 데드락
둘 이상의 쓰레드가 lock을 획득하기 위해 대기하는데, 이 lock을 잡고 있는 쓰레들도 똑같이 다른 lock을 기다리면서 서로 block상태에 놓이는 것을 말한다.
데드락은 다음 네가지 조건이 동시에 성립할 때 발생한다.
1. 상호 베제(Mutual Exclusion) - 자원은 한 번에 한 프로세스만 사용할 수 있어야 한다.
2. 점유 대기(Hold and wait) - 프로세스에 할당된 자원을 가진 상태에서 다른 자원을 기다린다.
3. 비선점(No Preemption) - 프로세스가 어떤 자원의 사용을 끝날 때 까지 그 자원을 뺏을 수 없다.
4. 순환 대기(Circular wait) - 각 프로세스는 순환적으로 다음 프로세스가 요구하는 자원을 가지고 있다.
해결하는 방법
- 예방, 회피, 무시
예방 - 상호배제 조건 제거, 점유와 대기 조건 제거, 비선점 조건 제거, 환형 대기 조건 제거 등이 있으나 비용이 많이 든다.
회피 - circular wait가 발생하지 않도록 자원 할당 상태를 검사한다, 자원 할당 그래프 알고리즘, 은행원 알고리즘 등이 있다.
무시 - 데드락 상황을 고려하는 것에 대한 비용이 낮다면 별다른 조치를 하지 않을 수 있다.
Reference
www.tcpschool.com/java/java_thread_concept
sujl95.tistory.com/63
yadon079.github.io/2021/java%20study%20halle/week-10
parkadd.tistory.com/48