[java] java에서 thread
Java에서 Thread
먼저, 멀티 태스킹이 무엇인지 알 필요가 있다.
최근의 OS는 멀티 태스킹을 지원하지 않는게 없다.
멀티 태스킹은 두 가지 이상의 작업을 동시에 하는 것을 말한다.
예를 들어, 컴퓨터로 음악을 들으며, 웹서핑을 하는 것이다.
실제로 동시에 처리될 수 있는 프로세스의 개수는 CPU 코어의 개수와 동일한데, 이보다 많은 개수의 프로세스가 존재하기 때문에 모두 함께 동시에 처리될 수는 없다.
각 코어들은 아주 짧은 시간 동안 여러 프로세스를 번갈아가며 처리하는 방식을 통해 동시에 동작하는 것처럼 보이게 할 뿐이다.
이와 마찬가지로, 멀티 스레딩에 대해 간단히만 알아보자.
멀티 스레딩은 하나의 프로세스 안에서 여러 개의 스레드가 동시에 작업을 수행하는 것을 말한다.
- 스레드 : 하나의 작업 단위.
스레드 구현 방법
자바에서 쓰레드를 구현하는 방법은 2가지가 있다.
- Runnable 인터페이스 구현
- Thread 클래스 상속
두 방식 모두 run() 메소드를 오버라이딩 한다.
[Runnable 인터페이스 구현]
1
2
3
4
5
6
public class MyThread implements Runnable {
@Override
public void run(){
// 수행 코드.
}
}
- Runnable 인터페이스를 구현하므로 다른 클래스를 상속받을 수 있다.
- run() 메소드를 오버라이드 하면 된다.
- 다만, start() 메소드가 없기 때문에 Runnable 인터페이스를 구현한 클래스의 객체를 만들어 Thread를 생성할 때, 생성자의 매개변수로 넘겨주고 쓰레드 객체의 start() 메소드를 수행한다.
1
2
3
4
public static void main(String[] args){
Runnable runnable = new MyThread();
Thread t = new Thread(runnable, "mythread");
}
[Thread 클래스 구현]
1
2
3
4
5
6
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가지의 상태를 가지고 있다.
- NEW : 스레드가 생성되고 아직 start()가 호출되지 않은 상태
- RUNNABLE : 실행 중 또는 실행 가능 상태
- BLOCKED : 동기화 블록에 의해 일시정지된 상태(lock이 풀릴 때까지 기다림)
- WAITING, TIME_WAITING : 실행 가능하지 않은 일시정지 상태
- TERMINATED : 스레드 작업이 종료된 상태
스레드를 사용하는 것이 효율적이라 하지만, 어렵다.
이유는 위와 같이 다양한 상태를 가지고 있으며, 이를 잘 사용하기 위해선 동기화와 스케줄링이 필요하기 때문이다.
- 스케줄링과 관련된 메소드 : sleep(), join(), yield(), interrupt()
- start() 이후에 join()을 해주면 main 스레드가 모두 종료될 때까지 기다려주는 일도 해준다.
이와 관련된 내용들은 깊은 내용이며, 운영체제와 연관성이 높다고 판단되어 운영체제 쪽에서 다룰 예정이다.