Thread

금은체리·2023년 10월 18일
1

Java

목록 보기
6/14

Process vs Thread

  • Process: 운영체제로부터 자원을 할당받는 작업의 단위
    * 실행중인 프로그램
  • Thread: Process가 할당받은 자원을 이용하는 실행의 단위
    * Process내에서 일하는 일꾼

Process 구조

  1. Code
  2. Data
  3. Memory
    • Stack
    • Heap

Thread

public class TestThread extends Thread {
				@Override
				public void run() {
							// Thread 수행작업
				}
}

...

TestThread thread = new TestThread(); // Thread 생성
thread.start() // 쓰레드 실행

Runnable

  • Runnable은 인터페이스이기 때문에 다른 필요한 class를 상속 받을 수 있음
public class TestRunnable implements Runnable {
				@Override
				public void run() {
							// 쓰레드 수행작업 
				}
}

...

Runnable run = new TestRunnable();
Thread thread = new Thread(run); // 쓰레드 생성

thread.start(); // 쓰레드 실행
  • 람다식: Runnable 인터페이스에 람다식을 사용하여 Thread를 구현
public class Main {
    public static void main(String[] args) {
        Runnable task = () -> {
            int sum = 0;
            for (int i = 0; i < 50; i++) {
                sum += i;
                System.out.println(sum);
            }
            System.out.println(Thread.currentThread().getName() + " 최종 합 : " + sum);
        };

        Thread thread1 = new Thread(task);
        thread1.setName("thread1");
        Thread thread2 = new Thread(task);
        thread2.setName("thread2");

        thread1.start();
        thread2.start();
    }
}
  • setName(): Method가 Thread에 이름을 부여함
  • Thread.currentThread().getName(): 현재 실행중인 Thread의 이름을 반환

멀티 Thread

  • 2개의 Thread는 서로 번갈아가면서 수행됨
  • 두 Thread의 실행 순서나 걸리는 시간은 알 수 없음
    * OS의 스케줄러가 처리하기 때문
public class Main {
    public static void main(String[] args) {
        Runnable task = () -> {
            for (int i = 0; i < 100; i++) {
                System.out.print("$");
            }
        };
        Runnable task2 = () -> {
            for (int i = 0; i < 100; i++) {
                System.out.print("*");
            }
        };


        Thread thread1 = new Thread(task);
        thread1.setName("thread1");
        Thread thread2 = new Thread(task2);
        thread2.setName("thread2");

        thread1.start();
        thread2.start();
    }
}

데몬 Thread

  • 보이지 않는 곳에서 실행되는 가장 우선순위가 낮은 Thread
  • 다른 Thread가 모두 종료되면 강제 종료
public class Main {
    public static void main(String[] args) {
        Runnable demon = () -> {
            for (int i = 0; i < 1000000; i++) {
                System.out.println("demon");
            }
        };

        Thread thread = new Thread(demon);
        thread.setDaemon(true);

        thread.start();

        for (int i = 0; i < 100; i++) {
            System.out.println("task");
        }
    }
}

사용자 Thread

  • 보이는 곳에서 실행되는 가장 우선순위가 높은 Thread

Thread의 우선 순위

  • 우선 순위 종류
    1. 최대 우선순위(MAX_PRIORITY) = 10
    1. 최소 우선순위(MIN_PRIORITY) = 1
    2. 보통 우선 순위(NORM_PRIORITY) = 5
      • 기본 값이 보통 우선순위
  • 우선순위 Method
    1. setPriority(): Thread 우선순위 설정 가능
    1. getPriority(): 우선순위 반환하여 확인 가능

Thread 그룹

  • Thread들은 기본적으로 그룹에 포함되어 있음
  • 모든 Thread들은 반드시 하나의 그룹에 포함되어 있어야함
    * 그룹을 지정하지 않으면 ➡️ 자동으로 main 그룹에 포함됨
  1. Thread 그룹 생성
    • ThreadGroup 클래스로 객체를 만들어서 첫번째 매개변수로 넣으면 됨
// ThreadGroup 클래스로 객체를 만듭니다.
ThreadGroup group1 = new ThreadGroup("Group1");

// Thread 객체 생성시 첫번째 매개변수로 넣어줍니다.
// Thread(ThreadGroup group, Runnable target, String name)
Thread thread1 = new Thread(group1, task, "Thread 1");

// Thread에 ThreadGroup 이 할당된것을 확인할 수 있습니다.
System.out.println("Group of thread1 : " + thread1.getThreadGroup().getName());
  1. Thread 그룹으로 묶어서 Thread 관리
    • ThreadGroup 객체의 interrupt() method를 실행시키면 ➡️ 해당 그룹 Thread들이 실행대기 상태로 변경
// ThreadGroup 클래스로 객체를 만듭니다.
ThreadGroup group1 = new ThreadGroup("Group1");

// Thread 객체 생성시 첫번째 매개변수로 넣어줍니다.
// Thread(ThreadGroup group, Runnable target, String name)
Thread thread1 = new Thread(group1, task, "Thread 1");
Thread thread2 = new Thread(group1, task, "Thread 2");

// interrupt()는 일시정지 상태인 쓰레드를 실행대기 상태로 만듭니다.
group1.interrupt();

Thread 제어

  • sleep(): 현재 Thread를 지정된 시간동안 멈추게 함

    • 자기자신에 대해서만 멈추게 할 수 있음

    • Thread.sleep(ms); : ms(밀리초) 단위로 설정됨

    • 예외처리를 해야함
      sleep 상태에 있는 동안 interrupt()를 만나면 다시 실행되기 때문
      특정 Thread 지목해서 멈추게 하는 것은 불가능


  • interrupt(): 일시정지 상태인 Thread를 실행대기 상태로 만듦

    • Thread가 start()된 후 동작하다 interrupt()를 만나 실행하면 ➡️ interrupt() = true가 됨
    • inInterrupted(): 상태값 확인 method
    • sleep() 실행 중 interrupt()가 실행되면 예외 발생 ➡️ !Thread.currentThread().isInterrupted()로 interrupted 상태를 체크하면 오류 방지 가능
  • join(): 정해진 시간동안 지정한 Thread가 작업하는 것을 기다림

    • 시간을 지정하지 않았을 때는 지정한 Thread의 작업이 끝날 때를 기다림


  • yield(): 남은 시간을 다음 Thread에게 양보하고 Thread 자신은 실행대기 상태가 됨


  • synchronzed: 멀티 Thread는 여러 Thread가 한 Process의 자원을 공유해서 작업하기 때문에 서로에게 영향을 줄 수 있음 ➡️ 장애나 버그 발생 가능, 이러한 일을 방지하기 위해 한 Thread가 진행중인 작업을 다른 Thread가 침범하지 못하도록 막음
    * 동기화 하려면 다른 Thread의 침법을 막아야하는 코드들을 임계영역으로 설정해야함

    • 임계영역: Lock을 가진 단 하나의 Thread만 출입 가능 ➡️ 한 번에 한 Thread만 사용 가능


  • wait(): 실행중이던 Thread는 해당 객체의 대기실(waiting pool)에서 통지를 기다림


  • notify(): 해당 객체의 대기실(waiting pool)에 있는 모든 Thread 중에서 임의의 Threadaks 통지를 받음


  • wait(), notify() 순서 정리

    • 침범을 막은 코드를 수행하다가 작업을 더 이상 진행할 상황이 아니면 ➡️ wait()을 호출하여 Thread가 Lock을 반납하고 기다리게 할 수 있음 ➡️ 그럼 다른 Thread가 Lock을 얻어 해당 객체에 대한 작업이 가능해짐 ➡️ 추후에 작업을 진행할 수 있는 상황이 되면 notify() 호출 ➡️ 작업을 중단했던 Thread가 다시 Lock을 얻어 진행이 가능


  • Condition: wait() & notify()의 문제점인 waiting pool 내 Thread를 구분하지 못한다는 점을 해결한 것

    • wait() & notify() ➡️ Condition의 await() & signal()


  • ReentrantLock: 재진입 가능한 Lock

    • 특정 조건에서 Lock을 풀고, 나중에 다시 Lock을 얻어 임계영역으로 진입 가능


  • ReentrantReadWriteLock: 읽기 위한 Lock과 쓰기를 위한 Lock을 따로 제공함


  • StampedLock: ReentrantReadWriteLock에 낙관적인 Lock의 기능을 추가함

    • 낙관적인 Lock: 데이터를 변경하기 전에 Lock을 걸지 않음 ➡️ 읽기와 쓰기 작업 모두 빠르게 처리 가능
profile
전 체리 알러지가 있어요!

0개의 댓글