Java Programming for Complete Beginners - in28Minutes Official 강의중 정리내용입니다.

자바의 스레드(Thread)

메인스레드

  • 모든 자바의 애플리케이션은 메인스레드가 main() 메소드를 실행하면서 시작되고 main() 메소드의 마지막 코드를 실행하거나 return문을 만나면 종료된다.
  • 메인스레드는 필요에따라 작업스레드(Work Thread)를 만들어 병렬로 코드를 실행할 수 있다
    멀티스레드 애플리케이션(Multi Thread Application)
    - 실행중인 스레드가 하나라도 있으면 프로세스는 종료되지 않는다
    - 메인스레드가 종료되어도 다른 작업이 작업스레드에서 실행중인 경우 프로세스는 종료되지 않는다.

작업스레드

작업스레드도 객체로 생성되므로 클래스가 필요하다

  1. Thread 클래스로부터 작업스레드 직접 생성

    Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
    
        }
    });
    • Runnable 객체(Runnable은 작업스레드가 실행하는 코드를 가지고 있는 객체)를 인자로 대입하여 Thread 객체를 생성
    • Runnable인터페이스는 run() 메소드 하나만을 구현해야 하는 인터페이스이기 때문에 Thread 객체 생성시 람다 표현식을 통해 생성 가능하다
    Thread thread = new Thread(() -> System.out.println("Working Thread"));
  2. Thread 클래스를 상속하는 하위클래스로 구현, run() 메소드 오버라이드

    • 추가적인 메소드, 프로퍼티를 정의하여 사용할 수 있는 장점이 있다
      public class SomethindDoingThread extends Thread {
      
          @Override
          public void run() {
              ...
          }
      
          // 추가 로직 작성 가능
      		public void doSomething(){
      				...
      		}
      }

동시성 (Concurrency) vs 병렬성 (Parallelism)

  • 동시성 (병행성)
    • 멀티 작업을 위해 하나의 코어에서 멀티 스레드가 번걸아가며 실행하는 성질이다.
    • CPU 하나가 Time Sharing 기법을 통해 실제로 동시에 스레드가 실행되는 것은 아니지만, CPU 제어권을 매우 빠르게 스레드에게 줬다가 뺏으면서 사람이 보기에 마치 동시에 실행되는 것처럼 보이는 것을 뜻한다.
  • 병렬성
    • 멀티 작업을 위해 멀티 코어에서 개별 스레드를 할당 받아 동시에 실행하는 성질이다.
    • 정말 말 그대로 CPU 각자가 나뉘어서 각자의 일을 하여 실질적인 동시 작업을 수행하는 것을 뜻한다.

스레드 스케줄링

스레드의 개수가 코어의 수보다 많을 경우 어떤 스레드에게 CPU 제어권을 주어야 하는지 결정해야 하는데, 이를 스레드 스케줄링이라고 한다. 스레드 스케줄링에 의해 스레드들은 아주 짧은 시간에 번갈아가면서 자신이 run() 메소드를 조금씩 실행한다.

자바의 스레드 스케줄링

  1. 우선 순위 방식 : 프로그래머가 특정 스레드에게 우선 순위를 코드로 제어할 수 있다
    • Thread의 setPriority(Integer) 메소드를 통해 구현할 수 있다.
    • 우선 순위는 1에서부터 10까지 주어지는데, 숫자가 클수록 우선 순위가 높다.
  2. 라운드 로빈 방식 : JVM에 의해 우선순위가 정해지므로 코드로 제어할 수 없다.

Synchronized 메소드, 블록

임계 영역 (Critical Section) : 멀티스레드 애플리케이션에서 하나의 스레드만 접근, 실행할 수 있는 코드영역을 임계영역이라고 한다.

자바에서 임계영역을 지정하기 위해 synchronized 메소드, 블록 기능을 제공한다

  • 동기화 메소드
    public synchronized void method() {
        (임계 영역); // 단 하나의 스레드만 실행가능한 메소드
    }
    
    • 동기화 메소드는 메소드 어디든 붙일 수 있다
    • 스레드가 동기화 메소드를 실행하는 즉시 객체에 lock이 걸리고 동기화 메소드 실행을 종료해야 lock이 풀린다.

동기화 블록

일부 내용만 임계 영역을 만들고 싶다면 다음과 같이 동기화 블록을 만들 수 있다.

public voidmethod() {
// 여러 스레드가 실행 가능한 영역
    ...
synchronized(공유 객체) {
        임계 영역// 단 하나의 스레드만 실행
    }
// 여러 스레드가 실행 가능한 영역
    ...
}

스레드 상태에 따른 열거 상수

Thread.State 에 정의되어 있으며, 스레드 객체로부터 getState 메소드를 통해 값을 얻을 수 있다.

public enum State {
        /**
		 * A thread that has not yet started is in this state.
		 * 스레드 객체가 생성되었고, 아직 start() 메소드가 호출되지 않은 상태
         */
        NEW,

        /**
		 * A thread executing in the Java virtual machine is in this state.
		 * 실행 상태로 언제든지 갈 수 있는 상태
         */
        RUNNABLE,

        /**
		 * A thread that is blocked waiting for a monitor lock is in this state.
         * 사용하고자 하는 객체의 lock이 풀릴 때까지 기다리는 상태
         */
        BLOCKED,

        /**
		 * A thread that is waiting indefinitely for another thread to perform a particular action is in this state.
		 * 다른 스레드가 통지할 때까지 기다리는 상태
         */
        WAITING,

        /**
		 * A thread that is waiting for another thread to perform an action for up to a specified waiting time is in this state.
		 * 주어진 시간 동안 기다리는 상태
         */
        TIMED_WAITING,

        /**
		 * A thread that has exited is in this state.
		 * 실행을 마친 상태
         */
        TERMINATED;
    }

Thread 상태 제어

참조 블로그 : https://koey.tistory.com/262

메서드설명
interrupt()일시 정지 상태의 스레드에서 interruptedException 예외를 발생시켜, 예외 처리 코드(catch)에서 실행 대기 상태로 가거나 종료 상태로 갈 수 있도록 합니다.
notify(), notifyAll()동기화 블록 내에서 wait() 메서드에 의해 일시 정지 상태에 있는 스레드를 실행 대기 상태로 만듭니다.
resume()suspend() 메서드에 의해 일시 정지 상태에 있는 스레드를 실행 대기 상태로 만듭니다.이 메서드는 Deprecated 메서드로 권하지 않기 때문에 대신 notiry(), 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)동기화(Synchronized) 블록 내에서 스레드를 일시 정지 상태로 만듭니다. 매개 값으로 주어진 시간이 지나면 자동적으로 실행 대기 상태가 됩니다. 시간이 주어지지 않으면 notify(), notifyAll() 메서드에 의해 실행 대기 상태로 갈 수 있습니다.
suspend()스레드를 일시 정지 상태로 만듭니다. resume() 메서드를 호출하면 다시 실행 대기 상태가 됩니다.이 메서드는 Deprecated 메서드로 권하지 않기때문에 대신 wait() 메서드를 사용합니다.
yield()실행 중에 우선순위가 동일한 다른 스레드에게 실행을 양보하고 실행 대기 상태가 됩니다.
stop()스레드를 즉시 종료시킵니다.이 메서드는 Deprecated 메서드로 권하지 않습니다.

스레드 풀

스레드 풀은 작업 처리에 사용되는 스레드를 제한된 갯수만큼 정해놓고 작업 큐에 들어오는 작업들을 스레드가 하나씩 맡아 처리한다.

  • 병렬 작업의 폭증으로 인한 스레드의 폭증을 막기위해 스레드 풀을 사용한다
  • 작업처리가 끝난 스레드는 다시 작업 큐에서 새로운 작업을 가져와 처리한다.
  • 스레드의 전체 갯수를 일정하게 유지하기 때문에 애플리케이션의 성능저하를 방지할 수 있다

자바는 스레드 풀을 생성하고 사용할 수 있도록 java.util.concurrent 패키지에서

  • ExecutorService 인터페이스
  • ExecutorService 의 구현 객체를 생성하는 Executors 클래스를 제공한다.

병렬작업을 실행과 관련된 클래스 계층구조

Executor 인터페이스

Executor 인터페이스는 쓰레드 풀의 구현을 위한 인터페이스이다.

  • 등록된 작업(Runnable)을 실행하기 위한 인터페이스
  • 인터페이스 분리 원칙(Interface Segregation Principle)에 맞게 등록된 작업을 실행하는 책임만 갖는다.

ExecutorService 인터페이스

  • ExecutorService는 작업(Runnable, Callable) 등록을 위한 인터페이스이다.
  • Executor를 상속받아서 작업 등록 뿐만 아니라 실행을 위한 책임도 갖는다. 그래서 쓰레드 풀은 기본적으로 ExecutorService 인터페이스를 구현한다.
  • ThreadPoolExecutor가 ExecutorService의 구현체인데, ThreadPoolExecutor 내부에 있는 블로킹 큐에 작업들을 등록해둔다.

ExecutorService 구현체(Executors 인스턴스를 통해 구현객체 생성)

  • ThreadPoolExecutor
    • CachedThreadPool : 스레드를 캐싱하는 스레드 풀 (일정시간 작업이 없으면 Pool에서 스레드 해제), 필요할 때 필요한 만큼 스레드를 생성하고 해제하는 스레드 풀
    • FixedThreadPool : 고정된 개수를 가진 스레드 풀
    • SingleThreadExecutor : 한 개의 스레드로 작업을 처리하는 스레드 풀
  • ScheduledThreadPoolExecutor
    • ScheduledThreadPool : 일정시간 혹은 주기적으로 작업을 실행하는 스레드를 생성하는 스레드풀

작업 생성과 처리요청

작업생성

작업은 Runnalble 또는 Callable 인터페이스를 구현하여 생성한다.

  • Runnable
    • 작업 완료후에 리턴값이 존재하지 않는다

      Runnable task = new Runnable() {
          @Override
          public void run() {
              // 스레드가 처리할 내용
          }
      }
  • Callable
    • 작업 완료후에 리턴값이 존재한다

      Callable<T> task = new Callable<T>() {
          @Override
          public T call() throws Exception {
              // 스레드가 처리할 내용
              return T;
          }
      }

작업 처리요청

ExecutorService의 작업 큐에 Runnable 또는 Callable 객체를 넣는 행위를 말한다.

작업 처리 요청을 위해 ExecutorService는 2가지 메소드를 제공한다.

  • execute(Runnable command)
    • 리턴 타입 ⇒ void
    • Runnable을 작업 큐에 저장하고, 작업 처리 결과를 받지 못함
    • 예외가 발생하면 해당 스레드를 스레드 풀에서 제거함
  • submit(Runnable task), submit(Runnable task, V result), submit(Callable task)
    • 오버로딩 케이스
      • submit(Runnable task)
      • submit(Runnable task, V result)
      • submit(Callable task)
    • 리턴 타입 => Future
    • Runnable 또는 Callable을 작업 큐에 저장
    • 리턴된 Future를 통해 작업 처리 결과를 알 수 있음
    • 예외가 발생하더라도 스레드는 종료되지 않는다 (다른 작업에 재사용될 수 있음)
  • invokeAll
    • 모든 결과가 나올 때 까지 대기하는 블로킹 방식의 요청
    • 동시에 주어진 작업들을 모두 실행하고, 전부 끝나면 각각의 상태와 결과를 갖는 List<Future>을 반환함
  • invokeAny
    • 가장 빨리 실행된 결과가 나올 때 까지 대기하는 블로킹 방식의 요청
    • 동시에 주어진 작업들을 모두 실행하고, 가장 빨리 완료된 하나의 결과를 Future로 반환받음

Future 인터페이스

Callable 인터페이스의 구현체인 작업(Task)은 가용 가능한 쓰레드가 없어서 실행이 미뤄질 수 있고, 작업 시간이 오래 걸릴 수도 있다. 실행 결과를 바로 받지 못하고 미래의 어느 시점에 얻을 수 있는데, 미래에 완료된 Callable의 반환값을 구하기 위해 사용되는 것이 Future이다. 즉, Future는 비동기 작업을 갖고 있어 미래에 실행 결과를 얻도록 도와준다. 이를 위해 비동기 작업의 현재 상태를 확인하고, 기다리며, 결과를 얻는 방법 등을 제공한다.

public interface Future<V> {

    boolean cancel(boolean mayInterruptIfRunning);

    boolean isCancelled();

    boolean isDone();

    V get() throws InterruptedException, ExecutionException;

    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}
  • get()
    • 블로킹 방식으로 결과를 가져온다
    • 타임아웃 설정 가능
  • isDone, isCandelled
    • isDone은 작업의 완료 여부, isCancelled는 작업의 취소 여부를 반환함
    • 완료 여부를 boolean으로 반환
  • cancel
    • 작업을 취소시키며, 취소 여부를 boolean으로 반환함
    • cancle 후에 isDone()는 항상 true를 반환함

0개의 댓글