loop-study
개발 공부할래?
loop-study
전체 방문자
오늘
어제
  • 분류 전체보기 (186)
    • 목표 및 회고 (25)
    • 세미나 & 워크샵 (1)
    • 교육 및 인강 (67)
      • TDD, Clean Code with Java (5)
      • ATDD, 클린 코드 with Spring (6)
      • DDD 세레나데 (3)
      • 인프라 공방 (6)
      • 이규원의 현실 세상의 TDD (19)
      • 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 (18)
      • 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 (0)
      • 모든 개발자를 위한 HTTP 웹 기본 지식 - 김영한 (8)
      • 코딩으로 학습하는 GoF의 디자인 패턴 (1)
      • 스프링 시큐리티 완전정복 6.x (1)
    • 서적 (62)
      • 객체지향의 사실과 오해 (1)
      • 객체지향과 디자인패턴 (7)
      • 만들면서 배우는 클린 아키텍처 (3)
      • 테스트 주도 개발로 배우는 객체 지향 설계와 실천 (1)
      • 오브젝트: 코드로 이해하는 객체지향 설계 (17)
      • 리팩토링 : 코드 구조를 체계적으로 개선하여 효율적인 리팩터링 구현하기 (0)
      • 토비의 스프링 (3)
      • 엔터프라이즈 애플리케이션 아키텍처 패턴 (9)
      • 개발자의 글쓰기 (1)
      • 소프트웨어 장인 (17)
      • Real MySQL 8.0 (2)
      • JVM 밑바닥까지 파헤치기 (0)
    • 개발 & 방법론 (29)
      • Java (13)
      • TDD (5)
      • ATDD (3)
      • DDD (6)
      • 인프라 (2)
      • SQL (0)
    • 개인이야기 (1)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

  • 백엔드 로드맵

인기 글

태그

  • 인프라공방
  • 백기선
  • 마틴 파울러
  • TDD
  • ATDD
  • 이규원
  • Patterns of Enterprise Application Architecture
  • java
  • 넥스트스탭
  • 모든 개발자를 위한 HTTP 웹 기본 지식
  • study
  • 스프링
  • 객체지향
  • JUnit
  • 조영호
  • 김영한
  • 엔터프라이즈 애플리케이션 아키텍처 패턴
  • 장인정신
  • fastcampus
  • 추상화
  • 오브젝트
  • 소프트웨어 장인
  • 테스트 주도 개발
  • nextstep
  • 자바
  • 인프런
  • DDD 세레나데
  • 스터디
  • 현실세상의 TDD
  • Test Driven Development

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
loop-study

개발 공부할래?

10주차 과제 : 멀티쓰레드 프로그래밍
개발 & 방법론/Java

10주차 과제 : 멀티쓰레드 프로그래밍

2021. 2. 18. 14:29

 

 

10주차 과제: 멀티쓰레드 프로그래밍 · Issue #10 · whiteship/live-study

목표 자바의 멀티쓰레드 프로그래밍에 대해 학습하세요. 학습할 것 (필수) Thread 클래스와 Runnable 인터페이스 쓰레드의 상태 쓰레드의 우선순위 Main 쓰레드 동기화 데드락 마감일시 2021년 1월 23일

github.com

목표

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

학습할 것 (필수)

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

Thread 클래스와 Runnable 인터페이스

프로세스와 스레드에 대해 알아보자

프로세스(Process)

운영체제에서 실행중인 하나의 애플리케이션을 프로세스라고 한다

프로세스는 운영체제로부터 할당받은 데이터와 메모리 등의 자원과 쓰레드로 실행된다

실행중인 프로세스, 사용중인 자원량도 표시된다

하나의 프로그램을 여러개 실행하면 멀티 프로세스라고 한다

멀티 프로세스는 누구나 자주 사용한다. 간단한 예시로는 크롬을 여러번 실행하는 경우가 있다. 

 

쓰레드(Thread)

프로세스가 가진 자원으로 작업을 하는 주체이다

프로세스는 하나 이상의 쓰레드를 가지며

두 개 이상의 쓰레드를 가진 프로세스를 멀티 쓰레드 프로세스라고 한다

 

 

쓰레드 생성과 실행 방법

 

생성은 2가지로 나눠진다

  • Runnable 인터페이스 사용
  • Thread 클래스 사용 (Runnable 인터페이스 구현체다)
// Runnable 인터페이스
class TestRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.print("r");
        }
    }
}

// Thread 클래스 상속
class TestThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.print("T");
        }
    }
}

 

실행방법은 둘 다 동일하다

start() 를 호출하면 된다

단, Runnable 구현체는 Thread 에 인스턴스를 넘겨주고 실행하자

public static void main(String[] args) {

    // 쓰레드 구현 및 실행 방법
    // 1. Runnable 인터페이스 사용
    TestRunnable r = new TestRunnable();
    Thread runnableSample = new Thread(r);

    // 2. Thread 클래스 사용
    TestThread threadSample = new TestThread();

    // start 로 실행한다
    runnableSample.start();
    threadSample.start();
}

 

실행 결과물

 

일반적인 흐름이라면 r 부터 100개 찍히고 T 가 실행되어야하는데 중간에 실행된 결과를 볼 수 있다.

 

여기서 start() 와 run() 의 차이점을 알아보자

run() 은 실행될 쓰레드가 작업할 로직을 구현하는 메소드다. 병렬 기능은 없다

start() 가 병렬처리를 해주는 메소드다. run() 을 실행하면서 위와 같은 결과를 나오게 해준다

public static void main(String[] args) {
	....
	runnableSample.run();
	threadSample.run();

	System.out.println("==============");
	System.out.println("==============");
	System.out.println("==============");

	runnableSample.start();
	threadSample.start();
}

run() 과 start() 의 차이

쓰레드 사용에 유의할 점이 존재한다

실행이 종료된 쓰레드는 다시 start() 를 할 수 없다. IllegalThreadStateException 발생한다

인스턴스를 재생성 후 실행해야한다.

 

 

쓰레드의 상태

쓰레드에는 다음과 같은 상태를 가진다

  1. NEW : 쓰레드 객체가 생성되고 start() 호출이 안된 상태
  2. RUNNABLE : 실행 대기중이며 언제든지 실행 상태로 변할 수 있다
  3. WAITING : 일시정지 상태로 다른 쓰레드의 중지를 대기하는 상태
  4. TIMED_WAITING : 일정시간 동안 일시정지 대기하는 상태
  5. BLOCKED : 공유중이 객체를 다른 쓰레드가 사용중일 때 락이 걸려서 풀리기 기다리는 상태
  6. TERMINATED : 실행을 마친 상태

 

다음은 쓰레드의 상태를 제어하는 메소드 내역이며, 취소선은 쓰레드의 안정성을 해친다고 비권장하는 메소드다

interrupt() 일시 정지 상태의 쓰레드에서 InterruptedException 예외를 발새시켜, 실행 대기 상태나 종료 상태로 만든다
notify()
notifyAll()
동기화 블록 내에서 wait() 에 의해 일시 정지 상태에 있는 스레드를 실행 대기 상태로 만든다
resume() - 비권장 suspend() 에 의해 일시 정지 상태인 쓰레드를 실행 대기 상태로 만든다.
비권장으로 notify(), notifyAll() 을 대신 사용한다
sleep(long millis)
sleep(long millis, int nanos)
주어진 시간 동안 쓰레드를 일시 정지 상태로 만든다. 주어진 시간이 지나면 자동적으로 실행 대기 상태가 된다.
join()
join(long millis)
join(long millis, int nanos)
join() 을 호출한 쓰레드는 일시 정지 상태가 된다. 실행 대기 상태로 가려면, join() 메소드를 가지는 쓰레드가 종료되거나, 매개값으로 주어진 시간이 지나야한다.
wait()
wait(long millis)
wait(long millis, int nanos)
동기화 블록 내에서 쓰레드를 일시 정지 상태로 만든다. 주어진 시간이 지나면 자동적으로 실행 대기 상태가 된다. 시간이 주어지지 않으면 notify() notifyAll() 에 의해 실행 대기 상태로 갈 수 있다
suspend() - 비권장 쓰레드를 일시 정지 상태로 만든다. resume() 으로 다시 실행 대기 상태가 된다
비권장으로 wait() 을 대신 사용한다
yield() 실행 중에 우선순위가 동일한 다른 쓰레드에게 실행을 양보하고 실행 대기 상태가 된다
stop() - 비권장 쓰레드를 즉시 종료한다

 

 

쓰레드 우선순위

쓰레드는 우선순위(priority)를 갖고 있다. 우선순위가 높은 쓰레드가 실행 상태를 더 많이 가진다

우선순위는 개발자가 코드로 제어가 가능하다. 우선순위 범위는 1~10 을 가지며 1 이 가장 낮고 10 이 가장 높다

우선순위가 없으면 디폴트로 5 의 값을 가지고 시작한다

쓰레드는 우선순위 상수를 제공하기도 한다

 

쓰레드가 제공하는 우선순위 상수

우선순위 제어는 간단하다

setPriority() 를 사용하면 된다

public static void main(String[] args) { 
    // 쓰레드 우선 순위 테스트
    for (int i = 1; i <= 30; i++) {
    	Thread thread = new PriorityTest("thread " + i);
        if (i != 30) {
            thread.setPriority(Thread.MIN_PRIORITY);
        } else {
            thread.setPriority(Thread.MAX_PRIORITY);
        }
      	thread.start();
    }
}


// 쓰레드 우선 순위 테스트용
class PriorityTest extends Thread {

    public PriorityTest(String name) {
        setName(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 2000000000; i++) {

        }

        System.out.println("Thread getName() = " + getName());
    }
}

우선순위 테스트 결과

 

Main 쓰레드

모든 Java 애플리케이션은 main 쓰레드로 시작한다

public static void main(String[] args) 가 바로 main 쓰레드다

main 쓰레드만으로 사용하면 싱글 쓰레드 애플리케이션이라 하고

다른 쓰레드를 생성해서 사용하면 멀티 쓰레드 애플리케이션이라 한다

public static void main(String[] args) {
    // 메인 쓰레드
    System.out.println(Thread.currentThread().getName());
}

 

 

동기화

싱글 쓰레드 애플리케이션에서는 한개의 쓰레드로 작업하니 객체 공유 문제가 없지만

멀티 쓰레드 애플리케이션에서는 여러개의 쓰레드가 하나의 객체를 공유해서 작업하는 상황이 생긴다

다음은 멀티 쓰레드 상태에서 하나의 객체를 공유했을 때 예제이다

public class ThreadStudyMain {
	public static void main(String[] args) {
    	// 동기화, 객체를 공유한다면?
        Calc calc = new Calc();

        Order1 order1 = new Order1();
        order1.setCalc(calc);
        order1.start();

        Order2 order2 = new Order2();
        order2.setCalc(calc);
        order2.start();
    }
}

class Calc {
    private int count;

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {

        }
        System.out.println(Thread.currentThread().getName() + " : " + this.count);
    }
}

class Order1 extends Thread {
    private Calc calc;

    public void setCalc(Calc calc) {
        this.setName("Order 1");
        this.calc = calc;
    }

    @Override
    public void run() {
        calc.setCount(5000);
    }
}

class Order2 extends Thread {
    private Calc calc;

    public void setCalc(Calc calc) {
        this.setName("Order 2");
        this.calc = calc;
    }

    @Override
    public void run() {
        calc.setCount(10000);
    }
}

order1 은 count 필드값 5000 으로 바꾸고 2초간 일시정지가 되었다

곧바로 order2 는 count 필드값 10000 으로 변경한다. 일시정지가 끝난 order1 실행이 되면?

order1 은 5000 이 아니라 10000 을 출력하게 된다.

 

이를 방지하기 위해 임계영역(critical section) 을 지정하게 된다. 자바에서는 임계영역을 지정하기 위해 동기화(synchronized) 메소드와 동기화 블록을 제공한다.

 

동기화 메소드는 메소드에 synchronized 키워드를 붙이면 끝난다

class Calc {
    ....
    public synchronized void setCount(int count) {
        this.count = count;
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {

        }
        System.out.println(Thread.currentThread().getName() + " : " + this.count);
    }
}

 

동기화 블록은 특정 영역만 동기화 시키는 것으로 다음과 같이 사용한다

    public void setCount(int count) {
        
        synchronized (this) { // 동기화 블록 synchronized(객체) 공유객체는 자기자신을 넣는다
            this.count = count;
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {

            }
            System.out.println(Thread.currentThread().getName() + " : " + this.count);
        }
    }

다른 쓰레드의 영향을 안 받고 원하던 결과가 나온다

 

 

데드락

교착상태라고 한다. 둘 이상의 쓰레드가 공유 객체 때문에 계속 기다리는 상태를 말한다

다음은 공유 객체를 서로 접근하려는 예제이다.

public class ThreadStudyMain {
    public static final Object LOCK_1 = new Object();
    public static final Object LOCK_2 = new Object();

    public static void main(String[] args) {
        LockThread1 lockThread1 = new LockThread1();
        LockThread2 lockThread2 = new LockThread2();
        lockThread1.start();
        lockThread2.start();
    }
    
    static class LockThread1 extends Thread {
        @Override
        public void run() {
            synchronized (LOCK_1) { // 동기화 블록 synchronized(객체) 공유객체는 자기자신을 넣는다
                System.out.println("lockThread1 : LOCK_1 잡음 ");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {

                }
                System.out.println("lockThread1 : LOCK_2 기다림 ");
                synchronized (LOCK_2) {
                    System.out.println("lockThread1 : LOCK_1 & LOCK_2 ");
                }
            }
        }
    }

    static class LockThread2 extends Thread {
        @Override
        public void run() {
            synchronized (LOCK_2) { // 동기화 블록 synchronized(객체) 공유객체는 자기자신을 넣는다
                System.out.println("lockThread2 : LOCK_2 잡음 ");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                }
                System.out.println("lockThread2 : LOCK_1 기다림 ");
                synchronized (LOCK_1) {
                    System.out.println("lockThread2 : LOCK_2 & LOCK_1 ");
                }
            }
        }
    }
}

과연 결과는 어떻게 나올까?

 

서로 끝나길 무한정 기다리는 데드락이 발생한다.

 

 

'개발 & 방법론 > Java' 카테고리의 다른 글

12주차 : 애노테이션  (0) 2021.02.25
11주차 과제 : enum  (0) 2021.02.18
9주차 : 예외처리  (0) 2021.02.14
자바 8주차 : 인터페이스  (0) 2021.02.13
7주차 : 패키지  (0) 2021.01.01
    '개발 & 방법론/Java' 카테고리의 다른 글
    • 12주차 : 애노테이션
    • 11주차 과제 : enum
    • 9주차 : 예외처리
    • 자바 8주차 : 인터페이스
    loop-study
    loop-study
    오늘도 공부하자

    티스토리툴바