[Java] Thread

클라우드·2024년 4월 4일
0

Java

목록 보기
10/20
post-thumbnail

1. 기본 용어

  • 많이 들어본 용어
    - process : 실행 중인 프로그램의 instance. 현재 실행 중인 프로그램을 지칭하는 용어
    - process가 동작하려면 당연히 resource가 필요하다.
    - 이 resource는 OS로 부터 할당 받는 메모리, CPU 시간, 디스크, 입출력 장치 등등
    - thread : 프로그램의 실행 흐름
    - process는 하나 이상의 Thread로 구성된다.
    - Thread는 프로세스 내에서 실행되는 작은 실행 단위라고 할 수 있다.
    - Thread는 process의 resource를 공유한다. (메모리를 공유함) ⇒ process의 효율성을 향상시킨다.
    - 만약, 둘 이상의 Thread를 가진 process라면 multi-thread process라고 한다.
    - multitasking : 시분할(time-slicing)기법을 이용해서 아주 짧은 시간동안 여러 process를 번갈아 가면서 수행시켜 마치 동시에 실행되는 것처럼 보이게 하는 기법
    - multiprocessing : 서로 다른 core가 서로 다른 process를 시간적으로 동시에 실행시키는 개념
    - multiprocessing (multithreading)
  • Hyper-Threading : 1 core에 여러 Thread 담당

2. Multithreading의 장, 단점

  • 장점 : CPU와 resource를 효율적으로 사용함으로 process의 실행 효율을 높일 수 있다. 결국, 사용자에 대한 응답성을 확보할 수 있다.
  • 단점 : resource를 공유하기 때문에 항상 동기화(Synchronization)에 신경을 써야 하고, 교착 상태(dead-lock) 상태에 빠질 수 있기 때문에 역시 이 부분도 신경을 써야 한다.

3. Thread를 구현해 보자

  • Process 안에서 Thread는 실행시키는 단위
  • method ≠ Thread
  • Thread 별로 콜 스택을 따로 따로 가지고 있다.
  • Stack은 Thread마다 별도로 할당이 된다.
  • Thread가 다 종료되어야 전체 프로그램이 종료된다.
  • 지금까지 작성했던 자바 프로그램은 사실 single thread process 였다.
  • main method를 호출해서 실행시키는 thread를 우리는 main thread라고 부른다.
  • 프로그램 종료는 프로그램(process)안에서 생성한 모든 thread가 종료되어야 전체 process가 종료된다.
  • 자바는 Thread 라는 class를 제공한다. 이 class를 이용해야만 Thread를 생성할 수 있다.

3.1 첫 번째 방법

  • 가장 쉬운 방법은 제공된 Thread class를 상속해서 사용자 정의 Thread class를 정의한 후, instance를 만들어서 사용하는 것이다.

3.2 두 번째 방법

  • Java가 제공해주는 Runnable이라는 interface를 구현해서 class를 만든 후, instance를 생성한다.
  • 그런 다음 Thread의 생성자에 runnable 객체를 인자로 주어서 객체를 생성하면 Thread가 만들어진다.
package test;

// 첫 번째 방법
class MyThread extends Thread { // Thread의 run()은 일반 메소드

    @Override
    public void run() {
        // Thread의 실행 코드
        System.out.println("여기는 첫 번째 Thread");
    }
}

// 두 번째 방법(이걸 더 많이 씀)
// 일반 추상 클래스는 객체를 만들지 않는다.
class MyRunnable implements Runnable { // Runnable의 run()은 추상 메소드
    // Thread의 실행 코드는 무조건 run() 안에 들어간다.
    @Override
    public void run() {
        // Thread의 실행 코드
        System.out.println("여기는 두 번째 Thread");
    }
}

public class ThreadTest {

    // main thread
    public static void main(String[] args) {
        MyThread t1 = new MyThread(); // Thread 생성
        t1.start(); // Thread 실행
        // start()는 non-blocking method
        // start()는 thread scheduler에게 thread를 실행시켜 달라고 요청하는 method
        // JVM이 thread scheduler를 가지고 있다.

        MyRunnable r = new MyRunnable(); // Runnable 객체
        Thread t2 = new Thread(r); // Thread 실행
        t2.start();
    }
}
package test;

public class ThreadTest2 {

    // main thread
    public static void main(String[] args) {
        // interface는 추상 method가 있어서 객체(new Runnable())를 만들 수 없다.
        // 객체를 만들 수 없는 이유는 추상 method가 있기 때문이다.
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("너무 복잡하다.");
            }
        }).start(); // new Thread부터 .start() 전까지 Thread이다.

        new Thread(() -> {
                System.out.println("너무 복잡하다...");
            }
        ).start();
    }
}
  • 지금까지 동작을 그림(Thread의 상태 전이도)로 표현하면 아래와 같다.
package test;

class MyThread1 extends Thread {

    public MyThread1(String name) {
        // super(); 상위 클래스에서 인자가 없는 생성자
        super(name); // 상위 클래스에서 name 인자를 가진 생성자
    }

    @Override
    public void run() { // Thread 객체가 가지고 있는 run() 메소드
        for (int i=0; i<9; i++) {
            System.out.println(this.getName()); // 결국 this는 Thread
        }
    }
}

class MyRunnable2 implements Runnable {
    @Override
    public void run() {
        for (int i=0; i<9; i++) {
            System.out.println(Thread.currentThread().getName()); // 인스턴스가 없어도 쓸 수 있는 static(메소드 or 필드)
            // 글씨가 기울어져 있으면 static
        }
    }
}
public class ThreadTest3 {
    public static void main(String[] args) {

        MyThread1 t1 = new MyThread1("홍길동");
        MyRunnable2 r = new MyRunnable2();
        Thread t2 = new Thread(r);

        t1.start();
        t2.start();
        // Runnable(실행이 가능한) 상태로 Thread가 들어온다. Thread 큐로 들어온다.
        // Thread Scheduler가 Thread를 선택하고 Core가 붙어서 Running 상태로 만든다.
        // run이 끝나면 Thread가 Dead 상태가 된다.
        // 즉, 여러 가지 상태 전이가 일어난다.
        // 누가 먼저 실행 될지는 아무도 모른다.
    }
}
profile
안녕하세요 :)

0개의 댓글