본문 바로가기
Java

[자바 스터디] #10 - 멀티쓰레드 프로그래밍

by zannew 2021. 2. 28.

 목표

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

▣ 학습할 내용

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

 

 

▶10-1 Thread 클래스와 Runnable 인터페이스

쓰레드 공부 시작에 앞서 프로세스와 쓰레드에 대해 알아보았다.

▷ 프로세스(Process)란?

프로세스란 단순히 실행 중인 프로그램이라고 할 수 있다. 즉 사용자가 작성한 프로그램이 운영체제에 의해 메모리 공간을 할당받아 실행 중인 것을 말합니다. 이러한 프로세스들은 프로그램에서 사용되는 데이터와 메모리 등의 자원 그리고 스레드로 구성된다.

▷ 쓰레드(Thread)란?

쓰레드란 프로세스 내에서 실제로 작업을 수행하는 주체를 의미한다. 프로세스가 공장이라면 쓰레드는 일꾼에 비유할 수 있다. 모든 프로세스에는 한 개 이상의 스레드가 존재하여 작업을 수행한다. 하나의 프로세스가 가질 수 있는 쓰레드의 개수는 제한되어 있지 않지만 쓰레드가 작업을 수행할 때 개별적으로 (호출)스택을 필요로 하므로 프로세스의 메모리 한계에 따라 만들 수 있는 쓰레드 최대 개수가 정해진다. 또한, 두 개 이상의 스레드를 가지는 프로세스를 멀티쓰레드 프로세스(multi-threaded process)라고 한다.

▷ Thread 클래스와 Runnable 인터페이스

쓰레드를 구현하는 방법에는 두 가지가 있다.

1. Runnable인터페이스를 구현한다.

class RunnableExample implements Ruunnable {
	public void run(){
    	// 구현 내용 - Runnable의 run()을 구현
    }
}

2. Thread클래스를 상속받는다.

class ThreadExample extends Thread{
	public void run(){
    	// 구현 내용 - Thread의 run()을 오버라이딩
    }
}

둘 중 Runnable인터페이스 구현하는 방법이 더 많이 사용되곤 하는데, 재사용성이 높고 코드에 일관성을 나타낼 수 있기 때문에 보다 객체지향적인 방법이라고 한다. Thread클래스 역시 Runnable인터페이스를 구현한 클래스이므로 두 방법의 차이점을 인지하고 선택적으로 사용하면 될 것 같다. 

▷두 방법의 차이점

Thread가 다른 클래스로 확장될 필요가 있을 경우 Thread를 상속받아버리면 다른 클래스를 상속받을 수 없는 상황이 생기므로 Runnable 인터페이스를 구현하면 되고,

그렇지 않은 경우, Thread클래스의 메서드를 직접 호출할 수 없지만 Thread클래스의 static method인 currentThread()를 통해 참조할 수 있으므로 Thread클래스를 사용하는 것이 편하다.

public class ThreadMain1 {
    public static void main(String[] args) {
        Thread1 th1 = new Thread1();
        Runnable r = new Runnable1();

        Thread th2 = new Thread(r);

        th1.start();
        th2.start();

    }
}
public class Thread1 extends Thread{	// Thread를 상속받는 방법
    public void run(){
        for(int i=0;i<5;i++){
            System.out.println(getName());
        }
    }
}
public class Runnable1 implements Runnable{	//Runnable을 구현하는 방법
    @Override
    public void run() {
        for(int i=0;i<5;i++){
            System.out.println(Thread.currentThread().getName());
        }
    }
}

 

▶10-2 쓰레드의 상태

상태 설명
NEW 쓰레드가 생성되고 아직 start()가 호출되지 않은 상태
RUNNABLE 실행 중 또는 실행 가능한 상태
BLOCKED 동기화블럭에 의해 일시정지된 상태(lock이 풀릴 때까지 기다리는 상태)
WAITING,
TIMED_WAITING
쓰레드의 작업이 종료되지는 않았지만 실행가능하지 않은 (unrunnable) 일시정지 상태, TIMED_WAITING은 일시정지시간이 지정된 경우를 의미한다.
TERMINATED 쓰레드의 작업이 종료된 상태

※ 쓰레드의 상태는 자바1.5버전부터 추가된 Thread클래스의 getState()메서드를 통해 체크할 수 있다.

 

▶10-3 쓰레드의 우선순위

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

예를 들어, 파일 전송 기능이 있는 메신저의 경우, 파일 다운로드를 처리하는 쓰레드보다 채팅 내용 전송 쓰레드가 우선순위가 더 높아야 사용자가 채팅하는데 불편함이 없게 된다. 대신 파일 다운로드 작업에 걸리는 시간은 상대적으로 더 길어질 것이다. 이처럼 시각적인 부분이나 사용자에게 빠르게 반응해야하는 작업을 하는 쓰레드의 우선순위를 다른 작업을 수행하는 쓰레드보다 높게 설정해야 한다. 

▷ 우선순위 관련 메서드

메서드명 설명
void setPriority(int newPriority) 쓰레드의 우선순위를 지정한 값으로 변경
int getPriority() 쓰레드의 우선순위를 리턴

위의 두 메서드를 통해 우선순위 값을 얻거나, 우선순위를 지정한 값으로 변경할 수 있다. 쓰레드가 가질 수 있는 우선순위의 범위는 1~10까지 이며 숫자가 높을 수록 우선순위가 높다. 

▷ 우선순위 관련 필드

필드명 설명
static final int MAX_PRIORITY 최대 우선순위를 명시
static final int MIN_PRIORITY 최소 우선순위를 명시
static final int NORM_PRIORITY 기본 우선순위를 명시

쓰레드의 우선순위가 가진 값은 상대적인 값이기 때문에 우선순위 값이 높을 수록 보다 보다 많이 실행 큐에 포함되고, 보다 많은 작업 시간을 할당받는다. 멀티코어라고 해도 OS마다 다른 방식으로 스케쥴링 하기 때문에, 어떤 OS에서 실행하느냐에 따라 다른 결과가 나올 수 있다. 자바는 우선순위에 따른 쓰레드의 처리 방식에 대해 강제하지 않으므로 우선순위에 관련된 구현이 JVM마다 다를 수 있다는 것을 의미한다. 그래서 우선순위를 지정한다해도 OS의 스케줄러에 종속적이라서 어느 정도 예측만 가능한 정도이며 정확히 알 수는 없다. 

 

▶10-4 Main 쓰레드

main메서드에서 작성을 수행하는 것도 쓰레드이다. 그리고 이를 main쓰레드라고 한다. 쓰레드를 일꾼으로 비유했을 때, 프로그램이 실행되기 위해 작업을 수행하는 일꾼이 최소 하나는 필요하다. 그래서 프로그램을 실행하면 기본적으로 하나의 일꾼(쓰레드)을 생성하고, 그 일꾼(쓰레드)이 main메서드를 호출해서 작업이 수행되도록 하는 것이다. 

main메서드 종료 후 호출스택

main메서드가 수행을 마치면 프로그램이 종료되었으나, 위 그림과 같이 아직 다른 쓰레드가 작업을 마치지 않은 상태라면 프로그램이 종료되지 않는다.

실행 중인 사용자 쓰레드가 하나도 없을 때 프로그램은 종료된다.

추가적인 쓰레드없이 main메서드만 실행하는 것을 싱글쓰레드 애플리케이션이라고 한다. 이와 달리 main쓰레드에서 쓰레드를 생성하여 실행하는 것을 멀티쓰레드 애플리케이션이라고 한다.

 

▶10-5 동기화

 동기화(sychronization)란, 한 쓰레드가 진행 중인 작업을 다른 쓰레드가 간섭하지 못하도록 막는 것을 의미한다.

 싱글쓰레드 프로세스의 경우 프로세스 내에서 단 하나의 쓰레드만 작업하기 때문에 프로세스의 자원을 가지고 작업하는데 문제가 없지만, 멀티쓰레드 프로세스의 경우 여러 쓰레드가 같은 프로세스 내의 자원을 공유해서 작업하기 때문에 서로의 작업에 영향을 주게된다. 만일 제어권이 각 쓰레드를 오가면서 작업을 마쳤을 때, 의도했던 것과 다른 결과를 얻을 수 있다.

 이러한 일이 발생하는 것을 방지하기 위해 한 스레드가 특정 작업을 마치기 전까지 다른 쓰레드에 의해 방해받지 않도록 하는 것이 필요하다. 그래서 도입된 것이 '임계 영역(critical section)''잠금(lock)'이다.

 공유 데이터를 사용하는 코드 영역을 임계 영역으로 지정해놓고, 공유 데이터(객체)가 가지고 있는 lock을 획득한 단 하나의 쓰레드만 이 영역 내의 코드를 수행하게 한다. 그리고 해당 쓰레드가 임계 영역 내의 모든 코드를 수행하고 벗어나면서 lock을 반납해야만 다른 쓰레드가 반납된 lock을 얻어 임계 영역의 코드를 수행할 수 있게 된다. 

 ※ 자바에서는 synchronized블럭을 통해 쓰레드의 동기화를 지원했지만, 1.5버전부터 'java.util.concurrent.lock'와 'java.util.concurrent.atomic'패키지를 통해 다양한 방식으로 동기화를 구현할 수 있도록 지원한다.

 

▷ Synchronized키워드

synchronized는 자바 키워드 중 하나이기 때문에 변수명이나 클래스명으로 사용이 불가능하다. 사용방법은 메서드 자체를 synchronized로 선언하는 방법메서드 내의 특정 문장만 synchronized로 감싸는 방법이 있다.

 

 

 

▶10-6 데드락

 

 데드락이란, 둘 이상의 쓰레드가 lock을 획득하기 위해 대기하는데, 이 lock을 잡고 있는 쓰레드들도 똑같이 다른 lock을 기다리면서 서로 block상태에 놓이는 것을 말한다. 데드락은 다수의 쓰레드가 같은 lock을 동시에 다른 명령에 의해 획득하려할 때 발생할 수 있다. 예를 들어 두 쓰레드가 각 객체의 lock을 가지고 있고 서로의 lock을 획득하려고 대기중인 상태이며 쓰레드끼리 서로 이 사실 유무를 모르고 영원히 차단된 상태로 남아있게 된다. 이 상태를 데드락(교착상태)이라고 표현한다. 

 

백기선님의 자바 라이브 스터디 커리큘럼을 따라 공부하고 있습니다. 

잘못된 점이나 보충할 부분이 있으면 코멘트 남겨주세요

작은 조언이 저에겐 성장의 원동력이 됩니다 :-)

'Java' 카테고리의 다른 글

[자바 스터디] #14 - 제네릭  (0) 2021.03.10
[자바 스터디] #12 - 애노테이션  (0) 2021.03.01
[자바 스터디] #9 - 예외처리  (0) 2021.02.18
[자바 스터디] #7 - 패키지  (0) 2021.02.17
[자바 스터디] #8 - 인터페이스  (1) 2021.02.16

댓글