자바의 스레드 - ddingmin/be-was GitHub Wiki
-
운영 체제를 배웠다면 스레드의 개념은 익히 알 것이다.
-
스레드는 프로그램 수행의 가장 작은 단위를 의미한다.
-
스레드는 한 가지일을 하는 프로세스의 단점을 극복하고자 여러가지 일을 동시에 하기위해 탄생했다.
- 스레드는 다음과 같은 생성 주기를 갖는다.
- Runnable: 준비 상태로 CPU 를 점유하고 있지 않으며 대기하고 있는 상태이다.
- Running: 실행 상태로 CPU 가 점유하고, 실행중인 상태를 의미한다.
- Dead: 수행할 동작이 모두 실행이 완료된 상태를 의미한다.
-
Blocked: 실행 중에 일시 중단되어 CPU 점유를 잃은 상태를 의미한다.
wait()메서드를 통해 대기 상태로 전환되며, 대기 상태에서notify()메소드를 통해 Runnable 상태로 전환된다.
자바의 스레드 모델은 자바에서 동시성(Concurrency)를 구현하기 위해 탄생했다.
- JAVA 1.0 - 초기 버전에서는 기본적인 동작을 하는
Thread클래스가 존재했다. - JAVA 1.2 -
synchronized키워드를 통한 동기화 기능과wait(),notify(),notifyAll()메서드를 통해 스레드를 제어할 수 있게 되었다. - JAVA 5 -
java.util.concurrent패키지가 도입되었고,Callable과Future및Executor,ExecutorService,Executors등등 스레드 풀, 동시성 컬렉션 등 동시성 기능들이 대거 추가되었다. - JAVA 7 -
Fork / Join Framework추가로 병렬 프로그래밍에 대한 기능을 추가했다. - JAVA 8 -
CompletableFuture클래스를 통해 비동기 처리를 더욱 쉽게 다룰 수 있게 되었다. 이는 자바 5 에서 추가된Future의 단점들을 극복한 클래스라고 한다.
public class MyThread implements Runnable {
public MyThread() {
}
public void run() {
System.out.println(Thread.currentThread().getName());
}
}먼저 스레드를 사용하기 위해서는 Runnable 클래스를 상속 받아 사용한다.
Thread 클래스를 상속받게 되면, 다중 상속을 받을 수 없어 Runnable 클래스를 상속 받는 것이 권장된다.
run() 메서드에는 실제 작업 내용을 구현해주면 된다.
작업 내용에 적힌 Thread.currentThread().getName() 코드는 현재 스레드의 이름을 출력한다.
public class ThreadMain {
public static void main(String[] args) {
Thread thread = new Thread(new MyThread());
thread.start();
}
}실제로 스레드를 생성하고 사용해 동작을 살펴보자.
상속 받아 구현한 MyThread를 생성자에 넣고, start() 메서드를 통해 스레드를 생성해줄 수 있다.
// ********Thread-0다음과 같은 실행 결과가 나온다.
그렇다면 thread.run() 을 통해 직접 실행한 것과 무엇이 다를까?
public class ThreadMain {
public static void main(String[] args) {
Thread thread = new Thread(new MyThread());
thread.run();
}
}다음과 같이 코드를 변경한 뒤 실행해보았다.
// mainmain 에서 실행되었다.
위 사진을 참고하면 이해하기 쉬울 것 같다.
start() 메서드는 스레드를 생성한 뒤 그 스레드의 호출 스택에 run() 메서드를 넣는다.
하지만 run() 메서드를 직접 사용하게 되면 다른 스레드를 생성하지 않는다.
생성된 스레드는 OS의 스케쥴러에 따라 호출되어 실행된다.
public class ThreadMain {
public static void main(String[] args) {
Thread thread0 = new Thread(new MyThread());
Thread thread1 = new Thread(new MyThread());
thread0.start();
thread1.start();
}
}public void run() {
for (int i = 0; i < 10; i++) {
System.out.print(Thread.currentThread().getName() + " ");
}
}// Thread-1 Thread-1 Thread-1 Thread-1 Thread-1
// Thread-0 Thread-1 Thread-0 Thread-1 Thread-0
// Thread-1 Thread-0 Thread-1 Thread-0 Thread-1
// Thread-0 Thread-0 Thread-0 Thread-0 Thread-0 다음과 같이 코드를 변경해 실행해보면 스레드0 을 먼저 만들었음에도 스레드1 이 먼저 수행된다.
또한 스레드의 작업 내용이 모두 수행된 뒤 다른 스레드의 동작이 수행되는 것이 아닌, 중간에 순서가 엉켜있는 것을 알 수 있다.
이를 통해 스레드가 동작하도록 구현함을 알 수 있다.
앞서 설명했던 스레드의 동작주기와 엮어 생각해 본다면 순서가 엉켜있다는 사실이 당연한 결과임을 알 수 있다!
[참고 자료]