ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Java] 10주차 과제 : 자바의 멀티쓰레드 프로그래밍
    Java/온라인 자바 스터디 2021. 1. 23. 20:10
    반응형

    목표

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


    학습할 것 (필수)

    Thread 클래스와 Runnable 인터페이스

    쓰레드의 상태

    쓰레드의 우선순위

    Main 쓰레드

    동기화

    데드락


    Thread 클래스와 Runnable 인터페이스

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


    Runnable인터페이스를 구현하는 방법은 재사용성(rEeusability)이 높고 코드의 일관성(consistency)을 유지할 수 있다는 장점이 있기 때문에 보다 객체지향적인 방법이라 할 수 있겠다.

    1.Thread클래스를 상속

    class MyThread extends Thread {

    public void run() { // Thread클래스의 run()을 오버라이딩

    /* 작업 내용 */

    }

    }

    2.Runnable인터페이스를 구현

    class MyThread implements Runnable {

    public void run() { // Runnable인터페이스의 추상메서드 run()을 구현

    /* 작업 내용 */

    }

    }


    Runnable인터페이스는 run()메서드만 정의되어 있는 간단한 인터페이스이다. Runnable인터페이스를 구현하기 위해서 해야 할 일은 추상메서드인 run()의 몸통을 만들어 주는 것 뿐이다.

    public interface Runnable {

    public abstract void run();

    }

    쓰레드를 구현한다는 것은 위의 2가지 방법 중 어떤 것을 선택하건 간에, 쓰레드를 통해 작업하고자 하는 내용으로 run()의 몸통을 채우기만 하면 되는 것이다.


    쓰레드의 상태

    상태 

    설명 

    NEW

     쓰레드가 생성되고 아직 start()가 호출되지 않은 상태

    RUNNABLE

     실행 중 또는 실행 가능한 상태

    BLOCKED

     동기화블럭에 의해서 일시정지된 상태(lock이 풀릴 때까지 기다리는 상태) 

    WAITING,

    TIMED_WAITING 

     쓰레드의 작업이 종료되지는 않았지만 실행가능하지 않은(unrunnable) 일시정지상태, TIMED_WAITNG은 일시정지시간이 지정되 경우를 의미한다.

    TERMINATED

     쓰레드의 작업이 종료된 상태


    쓰레드의 상태


    1.쓰레드를 생성하고 start()를 호출하면 바로 실행되는 것이 아니라 실행 대기열에 저장되어 자신의 차례가 될 때 까지 기다려야한다. 실행대기열은 큐(queue)와 같은 구조로 먼저 실행대기열에 들어온 쓰레드가 먼저 실행된다.

    2.실행대기상태에 있다가 자신의 차례가 되면 실행상태가 된다.

    3.주어진 실행시간이 다되거나 yield()를 만나면 다시 실행대기상태가 되고 다음 차례의 쓰레드가 실행상태가 된다.

    4.실행 중에 suspend(), sleep(), wait(), join(), I/O block에 의해 일시정지상태가 될 수 있다. I/O block은 입출력작업에서 발생하는 지연상태를 말한다. 사용자의 입력을 기다리는 경우를 예로 들 수 있는데, 이런 경우 일시정지 상태에 있다가 사용자가 입력을 마치면 다시 실행대기상태가 된다.

    5.지정된 일시정지시간이 다되거나(time-out), notify(), resume(), interrupt()가 호출되면 일시정지상태를 벗어나 다시 실행대기열에 저장되어 자신의 차례를 기다리게 된다.

    6.실행을 모두 마치거나 stop()이 호출되면 쓰레드는 소멸된다.


    쓰레드의 우선순위

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


    예를 들어 파일전송기능이 있는 메신저의 경우, 파일다운로드를 처리하는 쓰레드보다 채팅내용을 전송하는 쓰레드의 우선순위가 더 높아야 사용자가 채팅하는데 불편함이 없을 것이다. 대신 파일다운로드 작업에 걸리는 시간은 더 길어질 것이다.


    이처럼 시각적인 부분이나 사용자에게 빠르게 반응해야하는 작업을 하는 쓰레드의 우선순위는 다른 작업을 수행하는 쓰레드에 비해 높아야 한다.


    쓰레드의 우선순위에 따른 할당되는 시간의 차이


    위의 그림에서 알 수 있듯이 두 쓰레드의 우선순위가 같다면, 각 쓰레드에게 거의 같은 양의 실행시간이 주어지지만, 우선순위가 다르다면 우선순위가 높은 th1에게 상대적으로 th2보다 더 많은 양의 실행시간이 주어지고 결과적으로 더 빨리 작업이 완료될 수 있다.


    쓰레드의 우선순위와 관련된 메서드와 필드는 다음과 같다.

    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; // 보통우선순위


    쓰레드가 가질 수 있는 우선순위의 범위는 1~10이며 숫자가 높을수록 우선순위가 높다. 그러나 우선순위의 높고 낮음은 절대적인 것이 아니라 상대적인 것임에 주의하자.

    예를 들어 같은 프로세스의 두 쓰레드의 우선순위를 각각 1과 2로 설정하는 것과 9와 10으로 설정하는 것은 같은 결과를 얻는다. 프로세스에게 주어진 실행시간을 두 쓰레드에게 어떤한 비율로 나누어 할당할 것인지는 쓰레드간의 우선순위 차이에 의해서 결정된다.

    한 가지 더 알아두어야 할 것은 쓰레드의 우선순위는 쓰레드를 생성한 쓰레드로부터 상속 받는다는 것이다. main메서드를 수행하는 쓰레드는 우선순위가 5이므로 main메서드 내에서 생성하는 쓰레드의 우선순위는 자동적으로 5가 된다.


    Main 쓰레드

    자바 어플리케이션이 실행되면, JVM은 main과 system이라는 쓰레드 그룹을 만들고 JVM운영에 필요한 쓰레드들을 생성해서 이 쓰레드 그룹에 포함시킨다. 예를 들어 main메서드를 수행하는 main이라는 이름의 쓰레드는 main쓰레드 그룹에 속하고, 가비지컬렉션을 수행하는 Finalizer쓰레드는 system쓰레드 그룹에 속한다.

    우리가 생성하는 모든 쓰레드 그룹은 main쓰레드 그룹의 하위 쓰레드 그룹이 되면, 쓰레드 그룹을 지정하지 않고 생성한 쓰레드는 자동적으로 main쓰레드 그룹에 속하게 된다.


    동기화

    자바에서는 키워드 synchronized를 통해 해당 작업과 관련된 공유데이터에 lock을 걸어서 먼저 작업 중이던 쓰레드가 작업을 완전히 마칠 때까지는 다른 쓰레드에게 제어권이 넘어가더라도 데이터가 변경되지 않도록 보호함으로써 쓰레드의 동기화를 가능하게 한다.


    synchronized는 다음과 같이 두 가지 방법으로 사용될 수 있으며 가능하면 메서드에 synchronized를 사용하는 메서드 단위의 동기화를 권장한다.

    1.특정한 객체에 lock을 걸고자 할 때

    synchronized(객체의 참조변수){

    // ...

    }

    2.메서드에 lock을 걸고자할 때

    public synchronized void calcSum() {

    // ...

    }

    synchronized블록의 경우 지정된 객체는 synchronized블럭의 시작부터 lock이 걸렸다가 블록이 끝나면 lock이 풀린다. 이 블록을 수행하는 동안은 지정된 객체에 lock이 걸려서 다른 쓰레드가 이 객체에 접근할 수 없게 된다.

    synchronized메서드의 경우에도 한 쓰레드가 synchronized메서드를 호출해서 수행하고 있으면, 이 메서드가 종료될 때까지 다른 쓰레드가 이 메서드를 호출하여 수행할 수 없게 된다.

    synchronized를 이용해서 객체에 lock을 걸어서 동기화를 처리하는 것은 좋은데 주의해야할 점이 있다. 그것은 바로 쓰레드가 교착상태(dead lock)에 빠질 수 있다는 것이다.


    데드락

    교착상태(dead lock)란 두 쓰레드가 lock을 건 상태에서 서로 lock이 풀리기를 기다리는 상황으로 작업이 진행되지 않고 영원히 기다리게 되는 상황을 말한다. 쓰레드가 교착상태에 빠지지 않도록 주의해서 프로그래밍해야 한다.

    쓰레드를 동기화할 때 동기화의 효율을 높이기 위해 wait()과 notify()를 함게 사용할 수 있다. 한 쓰레드가 객체에 lock을 걸고 어떤 조건이 만족될 때까지 기다려야하는 하는 경우, 이 쓰레드를 그대로 놔두면 이 객체를 사용하려는 다른 쓰레드들은 lock이 풀릴 때까지 같이 기다려야하는 상황이 발생한다. 이러한 비효율을 개선하기 위해서 wait()과 notify()를 사용한다. 한 쓰레드가 객체에 lock을 걸고 오래 기다리는 대신 wait()을 호출해서 다른 쓰레드에게 제어권을 넘겨주고 대기상태로 기다리다가 다른 쓰레드에 의해서 notify()가 호출되면 다시 실행상태가 되도록 하는 것이다.


    출처 : 자바의 정석


    참조 : https://github.com/whiteship/live-study/issues/10



    반응형

    댓글

Designed by Tistory.