동작하고 있는 프로그램을 프로세스(Process)라고 한다. 보통 한 개의 프로세스는 한 가지의 일을 하지만, 이 쓰레드를 이용하면 한 프로세스 내에서는 두 가지 또는 그 이상의 일을 동시에 할 수 있게 된다.
Thread
Test.java
public class Test extends Thread {
public void run() {
System.out.println("thread run.");
}
public static void main(String[] args) {
Test test = new Test();
test.start();
}
}
위 예제를 실행하면 "thread run." 이라는 문장이 출력 될 것이다. 하지만 위 처럼 쓰레드가 하나인 경우에는 도대체 쓰레드가 어떻게 동작하고 있는지 알 수가 없다.
다음과 같이 Test 클래스를 수정해보자.
public class Test extends Thread {
int seq;
public Test(int seq) {
this.seq = seq;
}
public void run() {
System.out.println(this.seq+" thread start.");
try {
Thread.sleep(1000);
}catch(Exception e) {
}
System.out.println(this.seq+" thread end.");
}
public static void main(String[] args) {
for(int i=0; i<10; i++) {
Thread t = new Test(i);
t.start();
}
System.out.println("main end.");
}
}
0 thread start.
2 thread start.
1 thread start.
3 thread start.
4 thread start.
6 thread start.
5 thread start.
7 thread start.
8 thread start.
main end.
9 thread start.
0 thread end.
1 thread end.
4 thread end.
2 thread end.
3 thread end.
7 thread end.
5 thread end.
6 thread end.
9 thread end.
8 thread end.
0번부터 9번 쓰레드까지 순서대로 실행되지 않고 실행 순서가 일정하지 않은걸 보면 쓰레드는 순서에 관계없이 동시에 실행된다는 사실을 알 수 있다.
Join
위 예제에선 쓰레드가 모두 시작되고 종료되기 전에 main 메소드가 먼저 종료되었다. 모든 쓰레드가 종료된 후 main메소드를 종료시키고 싶은 경우에는 어떻게 해야할까
public static void main(String[] args) {
ArrayList<Thread> threads = new ArrayList<Thread>();
for(int i=0; i<10; i++) {
Thread t = new Test(i);
t.start();
threads.add(t);
}
for(int i=0; i<threads.size(); i++) {
Thread t = threads.get(i);
try {
t.join();
}catch(Exception e) {
}
}
System.out.println("main end.");
}
생성되는 쓰레드를 담기 위해서 ArrayList 객체인 threads를 만든 후 쓰레드 생성시 생성된 객체를 threads에 저장한다. main 메소드가 종료되기 전에 threads 에 담긴 각각의 thread에 join 메소드를 호출하여 쓰레드가 종료될 때까지 대기하도록 변경하였다.
"main end." 라는 문자열이 가장 마지막에 출력되는 것을 확인 할 수 있다.
쓰레드 프로그래밍 시 가장 많이 실수하는 부분이 바로 쓰레드가 종료되지 않았는데 쓰레드가 종료된 줄 알고 그 다음 로직을 수행하게 만드는 일이다. 쓰레드가 종료된 후 그 다음 로직을 수행해야 할 때 꼭 필요한 것이 바로 이 join 메소드이다.
Runnable
import java.util.ArrayList;
public class Test implements Runnable {
int seq;
public Test(int seq) {
this.seq = seq;
}
public void run() {
System.out.println(this.seq+" thread start.");
try {
Thread.sleep(1000);
}catch(Exception e) {
}
System.out.println(this.seq+" thread end.");
}
public static void main(String[] args) {
ArrayList<Thread> threads = new ArrayList<Thread>();
for(int i=0; i<10; i++) {
Thread t = new Thread(new Test(i));
t.start();
threads.add(t);
}
for(int i=0; i<threads.size(); i++) {
Thread t = threads.get(i);
try {
t.join();
}catch(Exception e) {
}
}
System.out.println("main end.");
}
}
Thread를 extends 하던 것에서 Runnable을 implements 하도록 변경되었다. (Runnable 인터페이스는 run 메소드를 구현하도록 강제한다.) 그리고 Thread 를 생성하는 부분을 다음과 같이 변경했다.
Thread t = new Thread(new Test(i));
Thread의 생성자로 Runnable 인터페이스를 구현한 객체를 넘길 수 있는데 이 방법을 사용한 것이다. 이렇게 변경된 코드는 이 전에 만들었던 예제와 완전히 동일하게 동작한다. 다만 인터페이스를 이용했으니 상속 및 기타 등등에서 좀 더 유연한 프로그램으로 발전했다고 볼 수 있겠다.
'개발 > JAVA' 카테고리의 다른 글
[SpringBoot] Interceptor 설정 (0) | 2020.08.13 |
---|---|
[JAVA] 예외처리 Exception (0) | 2020.06.24 |
[JAVA] 다형성 (0) | 2020.06.21 |
[JAVA] 인터페이스 (0) | 2020.06.17 |
[JAVA] 생성자 (0) | 2020.06.15 |