
| Process | Thread |
|---|---|
| - process별로 자체 메모리를 갖는다. | - process내의 다른 thread와 메모리 공유한다. |
| - 개별 메모리로 인해 process간 통신이 느리다. | - 공유 메모리를 이용해 thread간 통신이 빠르다. |
| - Multi process 시스템에서는 process context switching시 이전 process의 메모리 및 스택 정보를 storage에 저장하는 swapping이 발생할 수 있으며, 이는 정보를 메모리에서 storage로 옮기거나 storage에서 메모리로 옮기는 작업이 수행되므로 비용이 많이 든다. | - thread간 context 전환은 공유 메모리로 인해 비용이 저렴하다. |
스레드 그룹은 동일한 프로세스 내부에서 실행되는 스레드 집합이다. 그들은 동일한 메모리를 공유한다. 따라서 프로세스 그룹 대신 스레드 그룹은 많은 작업을 병렬로 수행할 수 있다.
또한 스레드 간의 컨텍스트 전환은 프로세스간 전환보다 훨씬 빠르다. 컨텍스트 전환이란 시스템이 실행 중인 프로세스나 스레드에서 다른 실행 중인 스로세스나 스레드로 전환하는 방식이다.
스레드는 동일한 메모리 공간을 사용하므로, 그 중 하나가 메모리의 내용을 손상시킬 때마다 다른 스레드의 내용도 손상될 수 있다. 프로세스의 경우 OS는 서로로부터 보호한다. 그 중 하나가 자체 메모리 공간을 손상시키더라도 다른 프로세스는 영향을 받지 않는다.
또한 프로세스는 서로 다른 머신에서 실행될 수 있다. 반면 스레드는 동일한 머신에서 실행되어야 한다.
public class Counter {
String name;
int maxCount;
int interval = 1000;
int count;
public Counter(String name, int maxCount){
this.name = name;
this.maxCount = maxCount;
count=0;
}
public void run(){
while(count < maxCount){
try{
Thread.sleep(interval);
System.out.printf("%s : %d%n", name, count++);
}catch(InterruptedException e){
Thread.currentThread().interrupt();
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Counter counter1 = new Counter("counter_1", 5);
Counter counter2 = new Counter("counter_2", 5);
counter1.run();
counter2.run();
}
}
이 코드의 실행 결과는 다음과 같다.

Counter 클래스는 멀티스레딩을 수행하지 않는다. run() 메서드를 호출하면 단순히 메인 스레드에서 순차적으로 실행된다.
만약 진정한 멀티스레딩을 원한다면, Counter 클래스를 Thread 클래스를 상속받거나 Runnable 인터페이스를 구현해야 한다.
Thread클래스를 상속받거나 라이브러리 임포트를 받지 않았는데 어떻게 sleep() 메서드를 사용할 수 있는가?
Thread.sleep() 메서드를 사용하고 있지만, 이 메서드는 'Thread'클래스의 정적 메서드 이기 때문에 직접적으로 'Thread'클래스를 상속받지 않아도 사용할 수 있다
public class ThreadCounter extends Thread {
String name;
int maxCount;
int interval=1000;
int count;
public ThreadCounter(String name, int maxCount) {
this.name = name;
this.maxCount = maxCount;
count = 0;
}
@Override
public void run() {
while (count < maxCount) {
try {
Thread.sleep(interval);
System.out.printf("%s : %d%n", name, ++count);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public static void main(String[] args) throws InterruptedException {
ThreadCounter counter1 = new ThreadCounter("counter_1", 5);
ThreadCounter counter2 = new ThreadCounter("counter_2", 5);
counter1.start();
counter2.start();
Thread.sleep(1000);
}
이 코드의 실행결과는 다음과 같다.

Thread 클래스의 start() 메서드를 보면 출력 코드가 없다. Thread의 객체가 start() 메서드를 실행했는데 어떻게 출력이 되는가?
start() 메서드를 호출하면 새로운 스레드가 생성되고, 이 스레드에서 run() 메서드가 자동으로 실행된다.
그럼 ThreadCounter 클래스에서 run()을 구현할 필요가 없는가?
run() 메서드를 구현하는 것은 필수적이다. Thread 클래스를 상속받을 때, 스레드가 수행할 작업을 정의하기 위해 run() 메서드를 오버라이딩 해야 한다.
구현된 start() 메서드를 보면 안에 run() 메서드를 호출하거나 언급이 없고 run()과 완전 다른 메서드인데 왜 start()를 실행하면 run()을 오버라이딩 해야 하는가?
Thread 클래스의 start() 메서드는 JVM에게 새로운 스레드를 시작하라는 신호를 보내고, 그 스레드가 시작되면 run() 메서드를 실행한다. 이 과정은 JVM이 내부적으로 처리하므로 start() 메서드 내에서 run() 메서드를 직접 호출하지 않아도 run()이 자동으로 호출된다.
그렇다면 JVM을 설계할때부터 start()와 run() 의 관계를 염두하고 설계한것인가?
그렇다. JVM을 설계할 때 start()와 run() 의 역할을 분담했다. Thread클래스의 start() 메서드는 JVM에게 새로운 스레드를 시작하라는 신호를 보내고, 새로운 스레드가 생성되면 그 스레드의 run() 메서드를 호출하도록 되어있다.
Counter클래스 : Thread를 사용하지 않기 때문에 두 카운터가 순차적으로 실행됨.
ThreadCounter 클래스 : Thread를 사용하여 두 카운터가 동시에 실행될 수 있음.
Runnable 인터페이스는 1개의 메소드만을 갖는 함수형 인터페이스 이다. 이 인터페이스는 멀티 스레딩을 구현하는데 중요한 역할을 한다.
public class RunnableCounter implements Runnable {
String name;
int count;
int maxCount = 5;
int interval = 1000;
public RunnableCounter(String name, int maxCount) {
this.name = name;
this.maxCount = maxCount;
count = 0;
}
@Override
public void run(){
while(count < maxCount){
try{
Thread.sleep(interval);
System.out.printf("%s : %d%n", name, count++);
}catch(InterruptedException e){
Thread.currentThread().interrupt();
}
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new RunnableCounter("t_1", 5));
Thread t2 = new Thread(new RunnableCounter("t_2", 5));
t1.start();
t2.start();
}
}
실행결과는 다음과 같다.

정리가 깔끔해요 :)