solgitae 2022. 8. 13. 22:00
728x90

목표

자바의 멀티쓰레드 프로그래밍에 대해 학습하세요.

학습할 것

  • Thread 클래스와 Runnable 인터페이스
  • 쓰레드의 상태
  • 쓰레드의 우선순위
  • Main 쓰레드
  • 동기화
  • 데드락

1.Thread 클래스와 Runnable 인터페이스

쓰레드를 구현하는 방법은 Thread클래스를 상속받는 방법과 Runnable인터페이스를 구현하는 방법, 모두 두 가지가 있다. 어느 쪽을 선택해도 별 차이는 없지만 Thread클래스를 상속받으면 다른 클래스를 상속받을 수 없기 때문에, Runnable인터페이스를 구현하는 방법이 일반적이다.
1. Thread클래스를 상속

class MyThread1 extends Thread {
    public void run() { /* 작업내용 */
	}
}

2. Runnable인터페이스를 구현

class MyThread2 implements Runnable {
    @Override
    public void run() { /* 작업내용 */
	}
}

/* Runnable인터페이스를 구현한 경우, Runnable인터페이스를 구현한 클래스의 인스턴스를 생성한 다음, 
  이 인스턴스를 Thread클래스의 생성자의 매개변수로 제공해야 한다. */

2.쓰레드의 상태

쓰레드 프로그래밍을 하다보면 쓰레드의 행동을 직접 제어해야 할 경우가 간혹 생기게 되는데, 예를 들면 1000장의 문서를 복사해야한다고 가정한다. 이 때 공유된 프린터는 1개 밖에 없는데, 1000장을 전부 복사하는 동안 혼자서 쓸 수는 없을 것이다. 다른 사람들도 프린터를 사용해야 하니 말이다. 그럴 때 100장씩 복사를 하도록 하고, 그 때마다 다른 사람들이 사용하도록 통제할 수 있을 것이다. 이처럼 쓰레드도 상태를 제어해야 할 필요성이 있는데, 제어를 하기 전에 해당 쓰레드의 상태를 알아야 한다. 그래서 쓰는 것이 getState()메서드이다.

3.쓰레드의 우선순위

쓰레드는 우선순위라는 속성(멤버변수)를 가지고 있는데, 이 우선순위의 값에 따라 쓰레드가 얻는 실행시간이 달라진다. 쓰레드가 수행하는 작업의 중요도에 따라 쓰레드의 우선순위를 서로 다르게 지정하여 특정 쓰레드가 더 많은 작업시간을 갖도록 할 수 있다.
void setPriority(int newPriority)
int getPriority()

public static final int MAX_PRIORITY = 10 //최대 우선순위
public static final int MIN_PRIORITY = 1 //최소 우선순위
public static final int NORM_PRIORITY = 5 //보통 우선순위

4.Main 쓰레드

main메서드의 작업을 수행하는 것도 쓰레드이며, 이를 main쓰레드라고 한다. 우리는 지금까지 우리도 모르는 사이에 이미 쓰레드를 사용하고 있었던 것이다. 앞서 쓰레드가 일꾼이라고 하였는데, 프로그램이 실행되기 위해서는 작업을 수행하는 일꾼이 최소한 하나는 필요하지 않을까? 그래서 프로그램을 실행하면 기본적으로 하나의 쓰레드(일꾼)을 생성하고, 그 쓰레드가 main메서드를 호출해서 작업이 수행되도록 하는 것이다.

5.동기화

한 쓰레드가 진행 중인 작업을 다른 쓰레드가 간섭하지 못하도록 막는 것을 동기화(synchronization)이라고 한다. Java에서는 sychronized블럭을 이용해서 쓰레드의 동기화를 지원했지만, JDK1.5부터는 ‘java.util.concurrent.locks’와 ‘java.util.concurrent.atomic’ 패키지를 통해서 다양한 방식으로 동기화를 구현할 수 있도록 지원하고 있다.
싱글쓰레드 프로세스의 경우 프로세스 내에서 단 하나의 쓰레드만 작업하기 때문에 프로세스의 자원을 가지고 작업하는데 별문제가 없지만, 멀티쓰레드 프로세스의 경우 여러 쓰레드가 같은 프로세스 내의 자원을 공유해서 작업하기 때문에 서로의 작업에 영향을 주게 된다. 
만일 쓰레드A가 작업을 하던 도중에 다른 쓰레드B에게 제어권이 넘어갔을 때, 쓰레드A가 작업하던 공유데이터를 쓰레드B가 임의로 변경하였다면, 다시 쓰레드A가 제어권을 받아서 나머지 작업을 마쳤을 때 원래 의도했던 것과 다른 결과를 얻을 수 있다. 
이러한 일이 발생하는 것을 방지하기 위해서 한 쓰레드가 특정 작업을 끝마치기 전까지 다른 쓰레드에 의해 방해받지 않도록 하는 것이 필요하다. 그래서 도입된 개념이 바로 ‘임계영역’과 ‘잠금(락, Lock)’이다. 공유 데이터를 사용하는 코드 영역을 임계 영역으로 지정해놓고, 
공유 데이터(객체)가 가지고 있는 lock을 획득한 단 하나의 쓰레드만 이 영역 내의 코드를 수행할 수 있게 한다. 그리고 해당 쓰레드가 임계 영역 내의 모든 코드를 수행하고 벗어나서 lock를 반납해야만 다른 쓰레드가 반납된 lock를 획득하여 임계 영역의 코드를 수행할 수 있게 된다.

6.데드락

멀티 쓰레드 프로그래밍에서 동기화를 통해 락을 획득하여 동일한 자원을 여러 곳에서 함부로 사용하지 못하도록 하였다. 하지만 두 개의 쓰레드에서 서로가 가지고 있는 락이 해제되기를 기다리는 상태가 생길 수 있으며 이러한 상태를 **교착상태(deadlock)**이라고 한다.교착상태가 되면 어떤 작업도 실행되지 못하고 서로 상대방의 작업이 끝나기만 바라는 무한정 대기 상태이다.

7.프로세스와 쓰레드의 차이(번외)

프로세스란 간단히 말해서 ‘실행 중인 프로그램'을 말하고, 프로그램을 수행하는 데 필요한 데이터와 메모리 등의 자원 그리고 쓰레드로 구성되어 있으며 프로세스의 자원을 이용해서 실제로 작업을 수행하는 것이 바로 쓰레드이다.