내가 보려는 기술면접(Thread in Java)

gotcha!!·2023년 8월 28일
0

CS

목록 보기
32/41

먼저, 멀티 태스킹이 무엇인지 알 필요가 있다.

최근의 OS는 멀티 태스킹을 지원하지 않는게 없다.

멀티 태스킹은 두 가지 이상의 작업을 동시에 하는 것을 말한다.

예를 들어, 컴퓨터로 음악을 들으며, 웹서핑을 하는 것이다.

실제로 동시에 처리될 수 있는 프로세스의 개수는 CPU 코어의 개수와 동일한데, 이보다 많은 개수의 프로세스가 존재하기 때문에 모두 함께 동시에 처리될 수는 없다.

각 코어들은 아주 짧은 시간 동안 여러 프로세스를 번갈아가며 처리하는 방식을 통해 동시에 동작하는 것처럼 보이게 할 뿐이다.

이와 마찬가지로, 멀티 스레딩에 대해 간단히만 알아보자.

멀티 스레딩은 하나의 프로세스 안에서 여러 개의 스레드가 동시에 작업을 수행하는 것을 말한다.

  • 스레드 : 하나의 작업 단위.

스레드 구현 방법
자바에서 쓰레드를 구현하는 방법은 2가지가 있다.

1. Runnable 인터페이스 구현
2. Thread 클래스 상속
두 방식 모두 run() 메소드를 오버라이딩 한다.

[Runnable 인터페이스 구현]

public class MyThread implements Runnable {
  @Override
  public void run(){
    // 수행 코드.
  }
}

Runnable 인터페이스를 구현하므로 다른 클래스를 상속받을 수 있다.
run() 메소드를 오버라이드 하면 된다.
다만, start() 메소드가 없기 때문에 Runnable 인터페이스를 구현한 클래스의 객체를 만들어 Thread를 생성할 때, 생성자의 매개변수로 넘겨주고 쓰레드 객체의 start() 메소드를 수행한다.

public static void main(String[] args){
  Runnable runnable = new MyThread();
  Thread t = new Thread(runnable, "mythread");
}

[Thread 클래스 구현]

public class MyThread extends Thread {

  public void run(){
    // 수행 코드.
  }
}

Thread 클래스를 상속받으면 다른 클래스를 상속받지 못한다.(다중 상속 불가능 - 자바의 특징)
run() 메소드를 직접 구현해야 한다.
또한, Thread 클래스를 상속받으면 스레드 클래스의 메소드를 바로 사용할 수 있지만, Runnable 구현의 경우 Thread 클래스의 static 메소드인 currentThread()를 호출해 현재 스레드에 대한 참조를 얻어와야만 호출이 가능하다.

스레드의 실행
스레드의 실행은 run() 호출이 아닌 start() 호출로 해야 한다.

왜?

위에서 우리가 정의했던 메소드는 run()이다. 하지만, 실제로 스레드에게 작업을 시키려면 start()로 작업해야 한다고 한다. run() 메소드로 작업 지시를 하면 스레드가 일을 할까? 그렇지 않다. 두 메소드 모두 같은 작업을 한다. 하지만 run() 메소드를 사용한다면, 이건 스레드를 사용하는 것이 아니다.

Java에는 Call Stack이라는 것이 있다. 이 영역이 실질적인 명령어들을 담고 있는 메모리로, 하나씩 꺼내서 실행시키는 역할을 한다.

만약 동시에 두 가지 작업을 한다면, 두 개 이상의 콜 스택이 필요하게 된다. 스레드를 이용한다는 건, JVM이 다수의 콜 스택을 번갈아가며 일처리를 하고 사용자에게는 동시에 작업을 하는 것처럼 보여주는 것이다.

즉, run() 메소드를 이용한다는 것은 main()의 콜 스택 하나만 이용하는 것으로 스레드 활용이 아니다. (그냥 스레드 객체의 run이라는 메소드를 호출하는 것 뿐이게 된다.)

start() 메소드를 호출하면 JVM은 알아서 스레드를 위한 콜 스택을 새롭게 만들어주고 context switching을 통해 스레드답게 동작하도록 해준다.

결국, 우리는 새로운 콜 스택을 만들어 작업을 해야 스레드 일처리가 되는 것이기 때문에 start() 메소드를 써야 하는 것이다.

start()는 스레드가 작업을 실행하는 데 필요한 콜 스택을 생성한 다음 run()을 호출해서 그 스택 안에 run()을 저장할 수 있도록 해준다.

스레드의 실행 제어
스레드는 다음과 같이 5가지의 상태를 가지고 있다.

  1. NEW : 스레드가 생성되고 아직 start()가 호출되지 않은 상태
  2. RUNNABLE : 실행 중 또는 실행 가능 상태
  3. BLOCKED : 동기화 블록에 의해 일시정지된 상태(lock이 풀릴 때까지 기다림)
  4. WAITING, TIME_WAITING : 실행 가능하지 않은 일시정지 상태
  5. TERMINATED : 스레드 작업이 종료된 상태

스레드를 사용하는 것이 효율적이라 하지만, 어렵다.

이유는 위와 같이 다양한 상태를 가지고 있으며, 이를 잘 사용하기 위해선 동기화와 스케줄링이 필요하기 때문이다.

스케줄링과 관련된 메소드 : sleep(), join(), yield(), interrupt()
start() 이후에 join()을 해주면 main 스레드가 모두 종료될 때까지 기다려주는 일도 해준다.

profile
ha lee :)

0개의 댓글