[Java] thread - 1

eeminsu·2021년 11월 27일
0
post-thumbnail

자바의 정석을 통해 공부한 내용을 요약하였습니다.

1. 프로세스와 쓰레드

  • 프로세스(Process) : 실행 중인 프로그램
  • 쓰레드(Thread) : 프로세스의 자원을 이용해서 실제로 작업을 수행하는 것
  • 프로세스는 자원쓰레드로 구성되어 있다.
  • 모든 프로세스에는 최소한 하나 이상의 쓰레드가 존재한다.
  • 둘 이상의 쓰레드를 가진 프로세스를 multi-threaded process라고 한다.
  • 프로세스를 작업공간(공장), 쓰레드는 작업을 처리하는 일꾼으로 생각하면 이해하기 쉽다.
  • 프로세스의 메모리 한계에 따라 생성할 수 있는 쓰레드의 수가 결정된다.
    (쓰레드가 작업을 후행하는데 개별적인 메모리 공간이 필요하기 때문)

1-1. 멀티프로세스 vs 멀티스레드

  • 멀티 태스팅(멀티 프로세싱) : 동시에 여러 프로세스를 실행시키는 것
  • 멀티 쓰레딩 : 하나의 프로세스 내에 동시에 여러 쓰레드를 실행시키는 것
  • 프로세스를 생성하는 것 보다 쓰레드를 생성하는 비용이 적다.
  • 같은 프로세스 내의 쓰레드들은 서로 자원을 공유한다.
  • 프로세스의 성능이 단순히 쓰레드의 개수에 비례하는 것은 아니며 하나의 쓰레드를 가진 프로세스보다 두 개의 쓰레드를 가진 프로세스가 오히려 더 낮은 성능을 보일 수도 있다.

1-2. 멀티쓰레딩의 장단점

장점

  • CPU사용률 향상시킴
  • 자원을 보다 효율적으로 사용할 수 있음
  • 사용자에 대한 응답성이 향상
  • 코드가 간결해짐

단점

  • 동기화 주의
  • 교착상태(Deadlock) 주의
  • 단점은 모두 자원을 공유하면서 작업을 하기 때문에 발생할 수 있는 문제들이다.


2. 쓰레드의 구현과 실행

쓰레드를 구현하는 방법은 2가지가 있다.

  1. Thread클래스를 상속
  2. Runnable 인터페이스를 구현

Runnable인터페이스를 구현하는 방법은 재사용성이 높고 코드의 일관성을 유지할 수 있기 때문에 보다 객체지향적인 방법이라 할 수 있다.
또한 Java는 클래스의 다중상속이 허용되지 않기 때문에 인터페이스를 구현하는 것이 일반적이다.

// Thread 클래스를 상속
class MyThread extends Thread {
	public void run() { // 작업내용 } // Thread 클래스의 run()을 오버라이딩
}

// Runnable 인터페이스 구현
class MyThread implements Runnable {
	public void run() { // 작업내용 } // Runnable인터페이스의 run()을 구현
}

Runnable 인터페이스는 오로지 run()만 정의되어 있는 간단한 인터페이스이다.

Runnable r  = new ThreadEx1_2(); // Runnable을 구현한 클래스의 인스턴스를 생성
Thread   t2 = new Thread(r);	  // 생성자 Thread(Runnable target)

Runnable인터페이스를 구현한 경우 Runnable인터페이스를 구현한 클래스의 인스턴스를 생성한 다음, 이 인스턴스Thread클래스의 생성자 매개변수로 제공해야 한다.

class ThreadEx1_2 implements Runnable {
	public void run() {
		for(int i=0; i < 5; i++) {
		    System.out.println(Thread.currentThread().getName());
		}
	}
}

또한 Runnable을 구현하면 Thread클래스의 static메서드인 current Thread()를 호출하여 쓰레드에 대한 참조를 얻어 와야 호출이 가능하다.

static Thread currentThread() - 현재 실행중인 쓰레드의 참조를 반환
String getName() - 쓰레드의 이름을 반환

Thread(Runnable target, String name)
Thread(String name)
void setName(String name)

쓰레드의 이름은 위와 같은 생성자나 메서드를 통해서 지정 또는 변경할 수 있다.

2-1. 쓰레드의 실행 - start()

쓰레드를 생성하고 나서 start()를 호출해야만 쓰레드가 실행된다.

t1.start(); // 쓰레드 t1 실행
t2.start(); // 쓰레드 t2 실행

start()가 호출되었다고 해서 바로 실행되는 것은 아니다.
우선 대기 상태로 차례를 기다렸다가 차례가 되었을 때 실행된다.
물론 대기 큐에 아무것도 없을 때는 바로 실행상태가 된다.

쓰레드의 실행순서는 OS의 스케줄러에 의해 결정된다.

또한 한번 실행이 종료된 쓰레드는 다시 실행할 수 없다.
즉, start()는 한 번만 호출할 수 있다.

만약 쓰레드의 작업을 한번 더 수행할 경우 새로운 쓰레드를 생성한 후에
start()를 호출해야 한다.
새로운 쓰레드를 생성하지 않고 start()를 호출하게 된다면 예외가 발생한다.


3. start()와 run()

  • main메서드에서 run()을 호출하는 것생성된 쓰레드를 실행시키는 것이 아니라 단순히 클래스에 선언된 메서드를 호출하는 것 뿐이다.
  • start()는 새로운 쓰레드가 작업을 실행하는데 필요한 호출 스택(call stack)을 생성한 다음 run()을 호출해서 생성된 호출 스택에 run()이 첫 번째로 올라가게 된다.
  • 쓰레드가 종료되면 작업에 사용된 호출스택은 소멸된다.

쓰레드를 생성하고 start()를 호출한 후 호출스택의 변화

  1. main메서드에서 쓰레드의 start()를 호출
  2. start()는 새로운 쓰레드를 생성하고, 쓰레드가 작업하는데 사용될 호출스택을 생성
  3. 새로 생성된 호출스택에 run()이 호출되어 쓰레드가 독립적인 공간에서 작업을 수행
  4. 스케줄러가 정한 순서에 의해서 번갈아 가면서 실행
  • 호출스택에서 맨 위 메서드가 실행상태이고 아래에 있는 메서드들은 대기상태이다.
  • 그러나 쓰레드가 둘 이상일 때는 호출스택의 최상위에 있는 메서드일지라도 대기상태에 있을 수 있다.
  • main메서드가 수행을 마쳤다 하더라도 다른 쓰레드가 아직 작업을 마치지 않은 상태라면 프로그램이 종료되지 않는다.


4. 싱글쓰레드와 멀티쓰레드

  • 하나의 쓰레드로 2개의 작업을 수행하는 경우 하나의 작업이 완료된 후 다른 작업을 시작한다.
  • 두개의 쓰레드로 수행하는 경우 짧은 시간동안 2개의 쓰레드가 번갈아 가면서 작업을 수행하여 동시에 두 작업이 처리되는 것과 같이 느끼게 한다.
  • 작업을 수행한 시간은 거의 같다.
  • 오히려 2개의 쓰레드로 작업을 할 경우 Context switching으로 인해 시간이 더 걸리게 된다.
  • 싱글 코어에서 단순히 CPU만을 사용하는 계산 작업일 경우 오히려 멀티 쓰레드보다 싱글쓰레드로 프로그래밍하는 것이 더 효율적이다.
  • 두 쓰레드가 서로 다른 자원을 사용하는 작업의 경우에는 멀티쓰레드 프로세스가 더 효율적이다.

참고

profile
안되면 될 때까지

0개의 댓글