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 {
@Override
public void run() {
System.out.println("여기는 첫 번째 Thread");
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("여기는 두 번째 Thread");
}
}
public class ThreadTest {
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start();
MyRunnable r = new MyRunnable();
Thread t2 = new Thread(r);
t2.start();
}
}
package test;
public class ThreadTest2 {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("너무 복잡하다.");
}
}).start();
new Thread(() -> {
System.out.println("너무 복잡하다...");
}
).start();
}
}
- 지금까지 동작을 그림(Thread의 상태 전이도)로 표현하면 아래와 같다.
package test;
class MyThread1 extends Thread {
public MyThread1(String name) {
super(name);
}
@Override
public void run() {
for (int i=0; i<9; i++) {
System.out.println(this.getName());
}
}
}
class MyRunnable2 implements Runnable {
@Override
public void run() {
for (int i=0; i<9; i++) {
System.out.println(Thread.currentThread().getName());
}
}
}
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();
}
}