프로세스와 스레드
- 프로세스
프로세스는 간단히 말해서 실행 중인 프로그램을 나타낸다. 프로그램을 실행하면, OS로부터 실행에 필요한 자원을 할당받아 프로세스가 된다. 프로세스는 데이터와 메모리등의 자원, 그리고 스레드로 구성되어 있다.
- 스레드
프로세스의 자원을 이용해서 실제로 작업을 수행하는 것이 바로 스레드이다. 그래서 모든 프로그램에는 최소한 하나 이상의 쓰레드가 존재하며, 둘 이상의 쓰레드를 가진 프로세스를 '멀티 쓰레드 프로세스'라고 한다.
쉽게 말하면 프로세스는 회사, 스레드는 직원이라고 생각하면 된다.
하나의 프로세스가 가질 수 있는 쓰레드는 제한되어 있지 않지만 스레드가 작업을 수행하는데 개별적인 메모리공간(호출스택)을 필요로 하기 때문에 프로세스의 메모리 한계에 따라 생성할 수 있는 스레드의 수가 결정된다. ( 실제로는 프로세스의 메로리 한계에 다다를 정도로 많은 스레드를 생성하는 경우는 없다. )
멀티태스킹과 멀 티쓰레딩
- 멀티태스킹 ( multi-tasking )
일반적으로 사용하는 윈도나 유닉스를 포함한 대부분의 OS는 멀티태스킹을 지원한다. 즉 여러 개의 프로세스가 동시에 실행될 수 있다.
- 멀티쓰레딩
하나의 프로세스 내에서 여러 스레드가 동시에 작업을 수행하는 것이다. CPU의 코어가 한 번에 단 하나의 작업만 수행할 수 있으므로 실제로 동시에 처리되는 작업의 개수는 코어의 개수와 일치한다. 그러나 일반적으로 처리해야 하는 스레드의 수는 언제나 코어의 개수보다 많기 때문에 각 코어가 아주 짧은 시간 동안 여러 작업을 번갈아 가며 수행함으로써 여러 작업들이 모두 동시에 수행되는 것처럼 보이게 한다.
따라서 프로세스의 성능이 단순히 쓰레드의 개수에 비례하는 것은 아니며, 하나의 스레드를 가진 프로세스 보다 두 개의 쓰레드를 가진 프로세스가 오히려 낮은 성능을 보일 수도 있다.
멀티쓰레딩의 장점
- CPU의 사용률을 향상한다.
- 자원을 보다 효율적으로 사용할 수 있다.
- 사용자에 대한 응답성이 향상된다.
- 작업이 분리되어 코드가 간결해진다.
스레드의 구현과 실행
Thread는 Thread 클래스를 상속받거나 Runnable 인터페이스를 구현하는 방법으로 나뉜다. Thread 클래스를 상속받게 되면 다른 클래스를 상속받을 수 없기 때문에 일반적으로 Runnable 인터페이스를 구현하는 것이 일반적이다.
- Thread를 상속받는 방법
class MyThread extends Thread {
public void run() { /* 작업 내용 */} // Thread 클래스의 run() 을 오버라이딩
}
- Runnable 인터페이스를 구현하는 방법 ( 보다 객체지향적이다)
class MyThread implements Runnable {
public void runt() { /* 작업 내용 */ } // Runnable 인터페이스의 run() 을 구현
}
- 스레드의 실행 - start()
스레드를 실행하려면 start()를 호출해야 한다. 이때 한번 실행이 종료된 스레드는 다시 실행할 수 없다는 것이다. 즉, 하나의 스레드에 대해 start()가 한 번만 호출될 수 있다.
ThreadTest1 t1 = new ThreadTest1();
t1.start();
t1.start(); // error ! IllegalThreadStateException
start()와 run()의 차이
main 메서드에서 run()을 호출하는 것은 생성된 스레드를 실행시키는 것이 아니라 단순히 클래스에 선언된 메서드를 호출하는 것이다.
반면 start()는 새로운 스레드가 작업을 실행하는데 필요한 호출스택을 생성한 다음에 run()을 호출해서, 생성된 호출 스택에 run() 이 첫 번째로 올라가게 한다.
따라서 결과적으로는 start()를 쓰는 게 멀티 스레드를 쓰는 목적에 부합하다.
이렇게 말하면 사실 무슨 말인지 잘 안 와닿는다. 이걸 이해하려면 기본적으로 JVM의 구조를 알고 있어야 한다.
- JVM 구조
메서드 영역 ( method area ) | 프로그램 실행 중 어떤 클래스가 사용되면, JVM 은 해당 클래스의 클래스파일을 읽어서 분석하여 클래스에 대한 정보(클래스 데이터) 를 이곳에 저장한다. 이 때 , 그 클래스의 클래스 변수도 이 영역에 함께 생성 된다. |
힙 ( heap ) | 인스턴스가 생성되는 공간. 프로그램 실행 중 생성도는 인스턴스는 모두 이곳에 생성 된다. 즉, 인스턴스변수들이 생성되는 공간이다. |
호출스택 ( call stack 또는 excution stack ) | 호출스택은 메서드의 작업에 필요한 메모리 공간을 제공한다. 메서드가 호출 되면, 호출스택에 호출된 메서드를 위한 메모리가 할당되며, 이 메모리는 메서드가 작업을 수행하는 동안 지역변수들과 연산의 중간결과 등을 저장하는데 사용된다. 그리고 메서드가 작업을 마치면 할당되었던 메모리공간은 반환되어 비워진다. |
각 메서드를 위한 메모리상의 작업 공간은 서로 구별되며, 첫 번째로 호출된 메서드를 위한 작업공간이 호출 스택의 맨 밑에 마련되고, 첫 번째 메서드 수행 중에 다른 메서드를 호출하면, 첫 번째 메서드의 바로 위에 두 번째로 호출된 메서드를 위한 공간이 마련된다.
이때 첫 번째 메서드는 수행을 멈추고, 두 번째 메서드가 수행되기 시작한다. 두 번째로 호출된 메서드가 수행을 마치게 되면 두 번째 메서드를 위해 제공되었던 호출스택의 메모리공간이 반환되며 첫 번째 메서드는 다시 수행을 계속한다.
start()로 실행하게 되면 이 호출스택이 따로 할당이 된다, 하지만 run() 은 기존에 main() 메서드에 대해서 생성된 호출스택 위에 run()을 위한 공간이 추가되는 것뿐이다.
위 그림에서 start()가 호출스택에 적재되면서 새로운 호출 스택을 생성하는 것을 볼 수 있다.
'Dev > Java' 카테고리의 다른 글
Thread 를 알아보자 - Synchronized, Lock과 Condition, volatile (3/3) (0) | 2024.04.15 |
---|---|
Thread 를 알아보자 - mulit vs single, PrioirtyQueue, ThreadGroup, 실행제어( 2 / 3 ) (0) | 2024.04.01 |
열거형(enums) - 열거형을 통해 사용할 수 있는 방법들 (0) | 2024.03.25 |
지네릭스(Generics) - 지네릭스를 알면 API 문서를 읽기 쉽다! (0) | 2024.03.25 |
Comparator 와 Comparable 의 차이 - 객체 내부 비교와 외부 비교 관점 (0) | 2024.03.25 |