티스토리 뷰

14.1 멀티 스레드 개념

  • 운영체제는 실행 중인 프로그램을 프로세스로 관리
  • 멀티 태스킹은 두 가지 이상의 작업을 동시에 처리하는 것을 말함
  • 스레드는 코드의 흐름을 말함
  • 하나의 프로세스가 두 가지 이상의 작업을 처리할 수 있는 이유는 멀티 스레드가 있기 때문이다.
  • 멀티 프로세스가 프로그램 단위의 멀티 태스킹이라면 멀티 스레드는 프로그램 내부에서의 멀티 태스킹이라고 볼 수 있다.
  • 멀티 프로세스는 서로 독립적이므로 다른 프로세스에게 영향을 미치지 않음
  • 멀티 스레드는 프로세스 내부에 생성되기 때문에 하나의 스레드가 예외를 발생시키면 다른 스레드에도 영향을 미친다.

 

14.2 메인 스레드

  • 메인 스레드가 main()메소드를 실행하면서 실행
     main() 메소드 첫 코드부터 순차적으로 실행
  • 필요에 따라 추가 작업 스레드들을 만들어서 실행 가능
  • 싱글 스레드에서는 메인 스레드가 종료되면 프로세스 종료
  • 멀티 스레드에서는 실행 중인 스레드가 하나라도 있으면 프로세스는 종료되지 않는다.

 

14.3 작업 스레드 생성과 실행

  • 작업 스레드도 객체로 관리하므로 클래스가 필요

 

Thread 클래스로 직접 생성

  • Runnable구현 객체를 매개값으로 갖는 생성자를 호출
  • Runnable은 스레드가 작업을 실행할 때 사용하는 인터페이스
  • Runnable 안에 있는 run() 에 스레드가 실행할 코드를 재정의
public class BeepPrintDemo {
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                Toolkit toolkit = Toolkit.getDefaultToolkit();
                for (int i = 0; i < 5; i++) {
                    toolkit.beep();
                    try { Thread.sleep(500); } catch (Exception e) {}
                }
            }
        });

        // 스레드 시작
        thread.start();

        // 메인 스레드 시작
        for (int i = 0; i < 5; i++) {
            System.out.println("띵");
            try { Thread.sleep(500); } catch (Exception e) {}
        }
    }
}

 

Thread 자식 클래스로 생성

package beginnerJava.src.ch14;

import java.awt.*;

public class BeepPrintDemo2 {
    public static void main(String[] args) {
        // Thread의 익명 자식 객체로 작업 스레드를 정의
        Thread thread = new Thread() {
            @Override
            public void run() {
                Toolkit toolkit = Toolkit.getDefaultToolkit();
                for (int i = 0; i < 5; i++) {
                    toolkit.beep();
                    try { Thread.sleep(500); } catch (Exception e) {}
                }
            }
        };

        thread.start();

        for (int i = 0; i < 5; i++) {
            System.out.println("띵");
            try { Thread.sleep(500); } catch (Exception e) {}
        }
    }
}

 

14.4 스레드 이름

  • 스레드는 자신의 이름을 가지고 있다 -> 메인 스레드는 main / 작업 스레드는 자동적으로 Thread-n 
// 이 코드를 실행하는 스레드 객체 참조를 얻음
Thread thread = Thread.currentThread();

// 현재 실행하고 있는 스레드의 이름 확인
thread.getName();

// 스레드 이름 설정
thread.setName("스레드 이름");

 

14.5 스레드 상태

  • 스레드 객체를 생성(new)하고 start() 메소드를 호출하면 스레드가 실행되는 것이 아니라 대기 상태가 된다.
  • 실행 대기 상태에서 스레드는 CPU 스케줄링에 따라 CPU를 점유하고 run() 메소드를 실행  -> 실행 상태로 전환
  • 실행 스레드는 run() 메소드를 모두 종료하기 전에 실행 대기 상태로 돌아갈 수 있고 대기와 실행을 반복하면서 run() 메소드를 실행 
  • run() 메소드가 종료되면 종료 상태가 된다.
  • 실행 상태에서 일지 정지 상태로 가기도 하는데, 일시 정지 상태는 스레드가 실행할 수 없는 상태
  • 일시 정지 상태엣 실행 상태로 가기 위해서는 실행 대기 상태로 가야만 한다.

 

주어진 시간 동안 일시 정시 sleep()

  • 주어진 시간 동안 스레드를 일시 정지 상태로 만들고 시간이 지나면 자동적으로 실행 대기 상태가 된다.

 

다른 스레드의 종료를 기다림 join()

Thread A

// threadB 스레드 시작
threadB.start();

// threadB 스레드가 종료할 때까지 Thread A는 기다린다. (일시 정지 상태가 된다)
threadB.join();

// threadB 의 run() 메소드가 완료되면 Thread A의 일시 정지 상태가 풀려 다음 코드 실행
threadB.run()

 

다른 스레드에게 실행 양보 yield()

  • 실행 상태에서 다른 스레드에게 실행을 양보하고 실행 대기 상태가 된다.

 

14.6 스레드 동기화

  • 멀티 스레드는 하나의 객체를 공유해서 작업할 수도 있음
  • 스레드가 사용 중인 객체를 다른 스레드가 변경할 수 없도록 스레드의 작업이 끝날 때까지 객체를 잠글 수 있음 -> 동기화 메소드와 블록

 

동기화 메소드 및 블록 선언: synchronized 키워드 사용

// 동기화 메소드
synchronized void method(){
// 단 하나의 스레드만 실행
}

void mehtod2(){
// 여러 스레드 실행 가능
    synchronized(공유 객체) {
    // 단 하나의 스레드만 실행
    }
}

 

wait() 과 notify() 를 이용한 스레드 제어

  • wait(): 스레드를 일시 정지 상태로 만든다.
  • notify(): wait() 메소드로 인해 일시 정지된 스레드를 실행 대기 상태로 만든다.
  • 두 함수를 이요해 스레드를 교대로 번갈아 가며 실행
  • 이 두 메소드는 동기화 메소드 또는 동기화 블록 내에서만 사용 가능

 

14.7 스레드 안전 종료

  • 예전에는 강제 종료를 위해 stop() 메소드를 사용했으나 사용 중이던 리소스들이 불안전한 상태로 남겨지기 때문에 deprecated 되었다.
  • 스레드를 안전하게 종료하는 방법은 리소스들을 정리하고 run()메소드를 빨리 종료한다

 

방법1) 조건 이용 방법

  • 조건을 이용해 run()메소드 종료를 유도

 

방법2) interrupt()  메소드 이용 방법

  • 스레드가 일시 정지 상태에 있을 때 InterrruptedException예외를 발생시켜 run() 메소드를 정상 종료
  • 실행 대기 / 실행 상태일 때는 InterrruptedException 예외 발생하지 않음 

 

14.8 데몬 스레드

  • 주 스레드의 작업을 돕는 보조적인 역할을 수행하는 스레드
  • 주 스레드가 종료되면 데몬 스레드도 따라서 자동 종료
  • 주 스레드가 데몬이 될 스레드의 setDaemon(true)를 호출하면 된다.

 

14.9 스레드풀

  • 스레드의 개수가 늘고 CPU가 바빠져 메모리 사용량이 증가하면 애플리케이션의 성능 또한 저하된다
  • 스레드풀은 작업 처리에 사용되는 스레드를 제한된 개수만큼 정해놓고 작업 큐에 들어오는 작업들을 스레드가 하나씩 맡아 처리하는 방식

스레드풀 생성

// 60초 동안 스레드가 아무 작업을 하지 않으면 스레드를 풀에서 제거한다.
ExecutorService executorService = Executors.newCachedThreadPool();

// 생성된 스레드를 제거하지 않는다.
ExecutorService executorService = Executors.newFixedThreadPool(5);

// ThreadPoolExecutor로 스레드풀 생성 가능
ExecutorService executorService = new ThreadPoolExecutor(
        3,
        100,
        120L,
        TimeUnit.SECONDS,
        new SynchronousQueue<Runnable>()
);

 

  • 초기 수: 스레드풀이 생성될 떄 기본적으로 생성되는 스레드 수
  • 코어 수: 스레드가 증가된 후 사용되지 않는 스레드를 제거할 때 최소한 풀에서 유지하는 스레드 수
  • 최대 수: 증가되는 스레드의 한도 수 

스레드풀 종료

  • 데몬 스레드가 아니기 때문에 maid 스레드가 종료되도 작업을 처리하기 위해 계속 실행 상태로 남아 있음
  • shutdown(): 작업 큐에 대기하고 있는 모든 작업을 처리하고 스레드풀 종료
  • shutdownNow(): 남아있는 작업과는 상관없이 강제로 종료 . 작업 큐에 있는 미러치된 작업 목록을 리턴함

작업 생성과 처리 요청

  • Runnable: 작업 처리 결과를 리턴하지 않음
  • Callable: 작업 처리 결과를 얻을 수 있도록 Future를 리턴

 

최근에 올라온 글