본문 바로가기
Language/Java

자바 10주차 : 멀티쓰레드 프로그래밍

by CleanCoder 2021. 3. 5.

목표

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

학습할 것 (필수)

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

 

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

 

쓰레드(Thread)

 

  • 프로세스라는 작업공간에서 실제로 작업을 처리하는 일꾼
  • 프로세스의 자원을 이용해서 작업을 수행함
  • 모든 프로세스에는 최소한 하나 이상의 쓰레드가 존재
  • 쓰레드가 하나(싱글 쓰레드)
  • 둘 이상의 쓰레드(멀티 쓰레드)

멀티 태스킹(multi-tasking)

 

  • 대부분의 OS가 지원함
  • 여러 개의 프로세스가 동시에 실행될 수 있는것을 말함

 

멀티 쓰레딩(multi-threading)

 

하나의 프로세스 내에서 여러 쓰레드가 동시에 작업을 수행하는 것.

CPU의 코어(core)가 한 번에 단 하나의 작업만 수행할 수 있으므로, 실제로 동시에 처리되는 작업의 개수와 일치한다.

 

대부분 쓰레드의 수는 코어의 개수보다 훨씬 많기 때문에 각 코어가 아주 짧은 시간 동안 여러 작업을 번갈아 가며 수행함으로써 여러 작업들이 모두 동시에 수행되는 것처럼 보이게한다.

 

따라서 프로세스의 성능이 단순하게 쓰레드의 개수에 비례하는것은 아니며, 하나의 쓰레드를 가진 프로세스보다 두개의 쓰레드를 가진 프로세스가 오히려 더 낮은 성능을 보일 수 있다.

 

멀티 쓰레딩의 장점

 

  • CPU의 사용률을 향상시킴
  • 자원을 보다 효율적으로 사용할 수 있음
  • 사용자에 대한 응답성 향상
  • 작업이 분리되어 코드가 간결해짐

멀티 쓰레딩에서 주의할 점

 

여러 쓰레드가 같은 프로세스 내에서 자원을 공유하면서 작업을 하기 때문에 발생할 수 있는 동기화(synchronization), 교착상태(deadlock) 과 같은 문제들을 고려해서 신중히 프로그래밍 해야한다.

 

 

자바 스레드를 만들기 위해서는 두 가지 작업이 필요하다

  • 스레드 코드 작성
  • JVM에서 스레드를 생성하고 스레드 코드를 실행하도록 요청

스레드 코드를 작성하는 방법

  • Thread 클래스 이용
  • Runnable 인터페이스 이용

Thread 클래스를 상속받아 스레드 만들기

  • Thread 클래스의 경로명은 java.lang.Thread
  • Thread 클래스를 상속받아 원하는 스레드 코드를 생성할 수 있다.
  • Thread 클래스는 스레드를 만들고 유지 관리하기 위해 다양한 메소드를 제공
    • Thread 생성자
      • Thread() : 스레드 객체 생성
      • Thread(Runnable target) : Runnable객체인 target을 이용하여 thread객체 생성
      • Thread(String name) : 이름이 name인 스레드 객체 생성
      • Thread(Runnable target, String name) : Runnable 객체를 이용하여, 이름이 name인 스레드 객체 생성
    • Thread 메소드
      • void run() : 스레드가 실행할 부분
      • void start() : JVM에서 스레드 실행을 시작하도록 요청
      • void interrup() : 스레드 강제 종료
      • static void yield() : 다른 스레드에게 실행을 양보
      • void join() : 스레드가 종료할 때까지 기다림
      • long getId() : 스레드의 ID값 리턴
      • String getName() : 스레드의 이름 리턴
      • int getPriority() : 스레드의 우선순위값 리턴 ( 1- 10 사이)
      • void setPriority(int n) : 스레드의 우선순위 값을 n으로 변경
      • Thread.State getState() : 스레드의 상태 값 리턴
      • static void sleep(long mills) : 스레드는 mills 시간 동안 잔다. mills의 단위는 밀리초
      • static Thread currentThread() : 현재 실행중인 스레드 객체와 래퍼런스 리턴

Thread 클래스 작성

class TimerThread extends Thread{
  @Override
	public void run(){
    //실행할 것을 기술
  }
}

//사용
TimerThread th = new TimerThread();
th.start();
  • Thread 클래스 상속

    • extends 키워드를 사용해서 상속
  • run() 메소드 오버라이딩

    • run() 메소드에 작성된 코드를 스레드 코드라고 부른다.
    • 스레드는 run()에서부터 실행을 시작하고 run()이 종료하면 스레드도 종료한다.
    • run()을 오버라이딩 하지 않는다면 Thread클래스에 작성된 run()이 실행되며 run()은 아무런 일도 하지 않고 단순히 리턴하도록 작성되어있기 때문에 스레드가 바로 종료
  • 스레드 객체 생성

    • 스레드 객체의 생성은 그냥 객체의 생성에 불과함
    • 스레드는 다른 객체와는 다르게 JVM에 등록되어 JVM에 의해 스케쥴링 되어야 비로소 작동
  • 스레드의 시작 : start() 메소드 호출

    • Thread 클래스의 start() 메소드를 호출
    • start() 메소드는 Thread에 구현된 메소드이며 개발자가 오버라이딩 하면 안된다
    • start()메소드는 생성된 스레드 객체를 스케쥴링이 가능한 상태로 전환하도록 JVM에게 지시한다
    • 이후 스케쥴링에 의해 이 스레드가 선택되면 비로소 JVM에 의해 run() 메소드가 호출되어 실행을 시작

 

Runnable 인터페이스로 스레드 만들기

 

Runnable은 클래스가 아닌 인터페이스로써 경로명 java.lang.Runnable이며, 다음과 같이 추상 메소드를 run()하나만 가지고 있다.

interface Runnable{
	public void run();
}

아래는 Runnable 인터페이스의 run()을 구현한 TimerRunnable 클래스를 작성하고 스레드를 생성하는 코드임.

class TimerRunnable implements Runnable{
	int n = 0;
	@Override
	public void run(){
		while(true){
			System.out.println(n);
			n++;
			try{
				Thread.sleep(1000);
			}
			catch(InterruptedException e){
				return;
			}
		}
	}
}

//사용
Thread th = new Thread(new TimerRunnable());
th.start();
  • Runnable 인터페이스 구현

    • implements로 받아서 사용
  • run() 메소드 오버라이딩

    • run()메소드는 스레드 코드이며 run()이 종료되면 스레드도 종료된다
  • 스레드 객체 생성

    • Thread th = new Thread(new TimerRunnable());
  • start() 메소드 호출

    • th.start()는 생성된 스레드 객체 th를 스케쥴링이 가능한 상태로 전환하도록 JVM에게 지시하고 이후 JVM에 의해 이 스레드가 선택되면 이 스레드의 run() 메소드가 호출되고 실행

 

 

2. 쓰레드의 상태

 

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

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

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

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

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

 

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

2. 자기 차례가 되면 실행상태가 된다.

3. 할당된 실행시간이 다되거나 yield() 메소드를 만나면 다시 실행 대기상태가 되고 다음 쓰레드가 실행상태가 된다.

4. 실행 중에 suspend(), sleep(), wait(), join(), I/O block에 위해 일시정지상태가 될 수 있다.

    (I/O block은 입출력 작업에서 발생하는 지연상태를 말합니다. 사용자의 입력을 받는 경우를 예로 들수 있다.)

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

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

 

 

3. 쓰레드의 우선순위

JVM은 철저히 우선순위를 기반으로 스레드를 스케줄링한다. 가장 높은 우선순위의 스레드를 먼저 실행시킨다.

만약 동일한 우선순위일 경우에는 돌아가면서 실행시킨다.

우선순위이 체계

  • 최대값(MAX_PRIORITY) = 10
  • 최소값(MIN_PRIORITY) = 1
  • 보통값(NORMAL_PRIORITY) = 5

자바 응용프로그램이 실행될 때 처음으로 생성되는 main 스레드는 보통 값의 우선순위로 태어나며

자식 스레드는 생성될 때 부모 스레드의 우선순위 값을 물려받기 때문에 main스레드의 모든 자식 스레드는 보통값(5)의 우선순위를 가지고 탄생

 

우선순위의 변경이 필요할 때

void setPriority(int newPriority)

위의 코드로 우선순위 값을 변경

 

우선순위의 값이 필요할 때

int getPriority();

 

4. Main 쓰레드

VM은 자바 응용 프로그램을 실헹하기 전에 사용자 스레드를 하나 만들고 이 스레드로 하여금 main()을 실행하게 한다.

이것이 main스레드이고 실행 시작 주소는 main() 메소드의 첫 코드가 된다.

이렇게 되면 자바 응용 프로그램을 실행하게되면 main스레드와 JVM내에서 자동으로 생성된 가비지 컬렉션 스레드 2개의 스레드가 존재하게 된다

 

main스레드의 정보 출력

public class ThreadMainEx{
	public static void main(String [] args){
    //스레드 아이디얻기
    long id = Thread.currentThread().getId();
    //스레드 이름 얻기
    String name = Thread.currentThread().getName();
    //스레드 우선순위값 얻기
    int priority = Thread.currentThread().getPriority();
    //스레드 상태 값 얻기
    Thread.State s = Thread.curentThread().getState();
    
    System.out.println("현재 스레드 아이디 : "+id);
    System.out.println("현재 스레드 이름 : "+name);
    System.out.println("현재 스레드 우선순위 : "+priority);
    System.out.println("현재 스레드 값 : "+s);
    
  }
}

 

5. 동기화

  • 스레드 동기화란 공유 데이터에 접근하고자 하는 다수의 스레드가 서로 순서대로 충돌 없이 공유 데이터를 배타적으로 접근하기 위해 상호 협력하는 것을 말한다.

  • 공유 데이터에 대한 접근은 배타적이고 독점적으로 이루어져야 하며, 독점적으로 공유 데이터를 다루는 프로그램 코드를 임계 영역이라고 부른다

방법

  • synchronized로 동기화 블록 지정

    • 가장 쉬운 방법으로, 스레드가 공유 데이터를 접근할 때 하나씩 순차적으로 실행하도록 제어하는 기법

    • 하나의 스레드가 공유 데이터에 접근을 시작한 순간, 데이터를 잠가(lock) 다른 스레드가 대기하도록 하는 방식

    • synchronized키워드는 스레드 동기화를 위한 장치로써, 임의의 코드 블록을 동기화가 설정된 임계 영역으로 지정한다.

    • synchronized 블록은, 진입할 때 lock이 걸리고, 빠져나올 때 unlock 동작이 자동으로 이루어지도록 컴파일된다.

       

 

메소드 전체를 임계영역으로 지정

synchronized void add(){
  //현재 합을 알아내고
  int n = getCurrentSum();
  //10만큼 증가시키고
  n+=10;
  //증가된 결과를 기록한다
  setCurrentSum(n);
}
  • 이렇게 synchronized를 선언하면 add()메소드를 통째로 임계 영역으로 지정
  • add() 메소드가 호출되면 자동으로 동기화되고 한 스레드가 add() 메소드를 호출하여 실행하고 있는 도중에 다른 스레드가 add() 메소드를 호출하면 이 스레드는 첫 번째 스레드가 add() 실행을 마치고 완전히 빠져나오기까지 자동으로 대기하게 된다

 

코드 블록을 임계 영역으로 지정

void execute(){
	.....
  synchronized(this){
    int n = getCurrentSum();
    n += 10;
    setCurrentSum(n);
  }
  .....
}
  • 한 스레드가 synchronized 블록 내의 코드를 실행하고 있을 때 다른 스레드가 이 블록을 실행하고자 하면, 먼저 실행 중인 스레드가 synchronized 블록의 실행을 마칠 때까지 자동으로 대기
  • 여기서 synchronized(this)는 synchronized 블록에서 인자로 주어진 객체와 연계된 lock을 사용하도록 개발자가 지정한 것

 

 

 

 

6. 데드락

 

http://denninginstitute.com/itcore/processes/Dead.html

두가지 이상의 작업이 하나의 자원을 가지고 있으면서 상대방이 가진 자원을 서로 필요로 하고 있는 상태이다. 어떠한 작업도 실행되지 못하고 상대방의 작업이 끝나기만 바라는 무한정 대기하는 비정상적인 상태로 데드락 상태를 완전히 피할 수는 없지만 특정 규칙을 따르면 교착상태가 발생하는 경우를 최소화 할 수 있다.

데드락의 발생 조건

  1. 상호배제(Mutual exclusion)Permalink
    자원을 한 번에 한 프로세스만이 사용하는 경우.
  2. 점유대기(Hold and wait)Permalink
    최소한 하나의 자원을 점유하고 있으면서 다른 프로세스에 할당되어 사용하고 있는 자원을 추가로 점유하기 위해 대기하는 프로세스가 있는 경우.
  3. 비선점(No preemption)Permalink
    다른 프로세스에 할당된 자원은 사용이 끝날 때까지 강제로 빼앗을 수 없는 경우.
  4. 순환대기(Circular wait)Permalink
    각 프로세스는 순환적으로 다음 프로세스가 요구하는 자원을 가지고 있는 경우.

교착상태 해결방법

 

1. 교착 상태 예방

교착 상태는 상호 배제, 비선점, 점유와 대기, 원형 대기 라는 네 가지 조건을 동시에 충족해야 발생하기 때문에 이 중 하나라도 막는다면 교착 상태가 발생하지 않는다. 그러나 이 방법은 실효성이 적어 잘 사용되지 않는다.


2. 교착 상태 회피

자원 할당량을 조절하여 교착 상태를 해결하는 방식이다. 즉, 자원을 할당하다가 교착 상태를 유발할 가능성이 있다고 판단하면 자원 할당을 중단하고 지켜보는 것이다. 그러나 자원을 얼마만큼 할당해야 교착 상태가 발생하지 않는다는 보장이 없기 때문에 실효성이 적다.

 

3. 교착 상태 검출과 회복

교착 상태 검출은 어떤 제약을 가하지 않고 자원 할당 그래프를 모니터링 하면서 교착 상태가 발생하는지 살펴보는 방식이다. 만약 교착 상태가 발생하면 교착 상태 회복 단계가 진행된다.

 

 

 

 

'Language > Java' 카테고리의 다른 글

자바 12주차 : 애노테이션  (0) 2021.03.05
자바 11주차 : Enum  (0) 2021.03.05
자바 9주차 : 예외 처리  (0) 2021.02.14
JAVA 8주차 : 인터페이스  (0) 2021.02.14
JAVA 7주차 : 패키지  (0) 2021.02.14

댓글