스레드는 실행 중인 프로그램 내에서 또 다른 실행의 흐름을 형성하는 주체
이다.
다음과 같은 프로그램을 실행하면 JVM은 하나의 스레드를 생성해서 main 메소드의 실행을 담당한다.
public class CurrentThreadName {
public static void main(String[] args) {
Thread ct = Thread.currentThread(); // main 메소드 실행 스레드 참조
String name = ct.getName(); // 참조 스레드 이름 get
System.out.println(name);
}
}
// 실행 결과
main
스레드 생성을 위해 java.lang.Runnable
인터페이스를 구현하는 인스턴스를 생성한다.
Runnable
은 추상 메소드 하나만 존재하는 함수형 인터페이스
다.void run
만 가지고 있다.public class MakeThreadDemo {
public static void main(String[] args) {
Runnable task = () -> { // 스레드가 실행할 내용
int n1 = 10;
int n2 = 20;
String name = Thread.currentThread().getName();
System.out.println(name + ": " + (n1 + n2));
};
Thread t = new Thread(task); // 인스턴스 생성 시 run 메소드의 구현 내용 전달
t.start(); // 스레드 생성 및 실행
System.out.println("End " + Thread.currentThread().getName());
}
}
// 실행 결과
End main
Thread-0: 30
JVM은 스레드를 생성해서 Thread 인스턴스 생성 시 전달된 run 메소드를 실행한다.
실행 결과의 Thread-0
는 기본적으로 주어진 이름이다.
별도의 이름을 붙이고 싶다면 다음 생성자를 이용한다.
public Thread(Runnable target, String name)
실행 결과를 보면 main 스레드가 작업을 먼저 끝냈다.
스레드 생성에는 시간이 걸리므로 이러한 결과는 쉽게 나온다.
main 스레드가 작업을 종료하더라도 프로그램은 종료되지 않는다.
모든 스레드가 작업을 마치고 소멸되어야 프로그램이 종료된다.
스레드는 작업을 마치면(run 메소드의 실행 완료) 자동으로 소멸된다.
스레드가 동시에 실행되는 상황을 자세히 관찰하기 위해 Thread 클래스 다음 sleep
을 통해 스레드 실행 속도를 조금 늦춰 본다.
public class MakeThreadMultiDemo {
public static void main(String[] args) {
Runnable task1 = () -> { // 20 미만 짝수 출력
try {
for (int i = 0; i < 20; i++) {
if (i % 2 == 0) {
System.out.print(i + " ");
}
Thread.sleep(100); // 0.1초 대기
}
} catch (InterruptedException e) {
e.printStackTrace();
}
};
Runnable task2 = () -> { // 20 미만 홀수 출력
try {
for (int i = 0; i < 20; i++) {
if (i % 2 != 0) {
System.out.print(i + " ");
}
Thread.sleep(100); // 0.1초 대기
}
} catch (InterruptedException e) {
e.printStackTrace();
}
};
Thread t1 = new Thread(task1);
Thread t2 = new Thread(task2);
t1.start();
t2.start();
}
}
// 실행 결과
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
보통 스레드 하나에 CPU 코어 하나가 할당되어 동시에 실행이 된다.
동시에 실행이 이뤄지는 스레드를 대상으로 실행 흐름을 조절하거나 예측하는 것은 잘못된 결과가 나오기 쉽다.
스레드가 처한 상황에 따라서, OS가 코어를 스레드에 할당하는 방식에 따라서 두 스레드에 실행 속도는 차이가 있을 수 있다.
다음은 sleep
을 생략한 예제이다.
public class ThreadMultiNoSleepDemo {
public static void main(String[] args) {
Runnable task1 = () -> { // 20 미만 짝수 출력
for (int i = 0; i < 20; i++) {
if (i % 2 == 0) {
System.out.print(i + " ");
}
}
};
Runnable task2 = () -> { // 20 미만 홀수 출력
for (int i = 0; i < 20; i++) {
if (i % 2 != 0) {
System.out.print(i + " ");
}
}
};
Thread t1 = new Thread(task1);
Thread t2 = new Thread(task2);
t1.start();
t2.start();
}
}
// 실행 결과
0 2 1 3 5 7 9 11 13 15 17 19 4 6 8 10 12 14 16 18
0 1 2 3 4 5 7 6 9 8 11 10 13 12 15 14 16 18 17 19
0 1 2 3 5 4 6 8 10 12 14 16 18 7 9 11 13 15 17 19
세 번의 실행 결과 모두 다르다.
이것이 스레드의 실행 특성이다.
각 스레드는 독립적으로 일을 실행한다.
Thread를 상속하는 클래스를 정의해서 생성하는 방법이 있다.
public class MakeThreadDemo2 {
public static void main(String[] args) {
Task t1 = new Task();
Task t2 = new Task();
t1.start();
t2.start();
System.out.println("End " + Thread.currentThread().getName());
}
}
class Task extends Thread {
@Override
public void run() {
int n1 = 10;
int n2 = 20;
String name = Thread.currentThread().getName();
System.out.println(name + ": " + (n1 + n2));
}
}
// 실행 결과
Thread-0: 30
End main
Thread-1: 30
Thread
를 상속하는 클래스는 다음 메소드를 오버라이딩 해야 한다.
public void run()
start
메소드 호출을 통해 스레드가 생성됐을 때, 오버라이딩 한 run
메소드를 실행한다.