이번 프로젝트에서 서버 만드는 부분에 멀티 스레드를 이용하여 API 를 호출해야 할일이 있어
가장 기초인 Thread서부터 차근차근 공부하려고 Thread에 대해서 공부를 해봤다.
Thread란??
Thread 란 스레드(thread)란 실행중인 프로그램 내에서 실제로 작업을 수행하는 주체를 의미합니다.
모든 프로세스에는 한 개 이상의 스레드가 존재하여 작업을 수행합니다.
또한, 두 개 이상의 스레드를 가지는 프로세스를 멀티스레드 프로세스(multi-threaded process)라고 합니다.
말이 어렵게 써져있지만 작업을 하고 있는 인부라고 보면 된다.
일하는 인부가 한명이라면 여러개의 작업을 할때 아래 사진처럼 하나의 작업이 끝나고 다음 작업
이런 형식으로 작업을 하게 된다.
이처럼 스레드가 하나라면 3개의 작업을 하는데 시간이 엄청 길어지는것이다.
하지만 만약 3개의 작업을 3명의 인부가 하게 된다면 작업 시간은 엄청 단축 될것이다.
이와 마찬가지로 3개의 작업을 3개의 스레드가 하게 된다면 작업도 엄청 빨라질수 있다.
이렇게 작업을 나눠서 빨리 처리하고 싶을떄는 Thread를 생성하여서 작업을 처리하면 작업 처리속도가 훨씬 빨라진다.
그렇더면 Thread는 어떻게 생성하는걸까?
Thread 생성 방법
Thread를 생성하는데에는 2가지 방법이 있다.
1. Thread Class를 extends 받는 Class를 만든다.
2. Runnable Class를 implements 받는 Class 만든다.
위 2가지 방법중 주로 두번째 방법을 사용해서 Thread를 생성한다고 한다.
그래도 배워두면 좋으니 두가지 방법에 대해 모두 알아보자.
1. Thread Class를 extends 받는 Class를 만든다.
일단은 소스부터 살펴보자.
ThreadFirst class
package com.wook.thread;
public class ThreadFirst extends Thread{
public ThreadFirst(String threadName) {
super(threadName);
}
public void run() {
for(int i = 0; i<10;i++) {
System.out.println(this.getName()+ " : "+ i);
}
System.out.println();
}
}
위 소스에서 Thread를 extends 하였고 생성자로 문자열을 받아 Thread의 이름을 설정할수 있게 했다.
그리고 run()를 만들어 Thread가 해야할 작업을 적어 놨는데
그 안에는 1부터 10까지 수를 반복해서 출력을 하도록 했다.
그럼 이제 Thread에 대해 정의를 해놨으니 Thread를 만들어서 사용을 해보자.
ThreadTest class
package com.wook.thread;
public class ThreadTest {
public static void main(String[] args) throws InterruptedException{
// TODO Auto-generated method stub
//In thread running order is not prdeictable
//Thread created by extending Thread
//This method is not mainly used
System.out.println("Main Thread Start");
for(int i = 0; i<10;i++) {
new ThreadFirst("Thread"+i).start();
}
System.out.println("Main Thread End");
}
}
위 소스에 대해서 설명을 하자면 일단 Thread를 생성하는 MainThread가 시작되었음을 알리고
그 밑에 반복문은 0~9 까지의 반복문을 도는 동안 10개의 Thread를 생성하여
그 Thread를 start()하게 해서 위에서 만든 run() 가 실행될수 있게 했다.
그리고 Thread를 생성하는 Thread가 10개의 Thread를 모두 생성하고 start()하게 했으면
"Main Thread End" 를 출력하게 했다.
위의 결과는 아래와 같다.
Main Thread Start
Thread0 : 0
Thread0 : 1
Thread0 : 2
Thread2 : 0
Thread1 : 0
Thread2 : 1
Thread0 : 3
Thread0 : 4
Thread4 : 0
Thread4 : 1
Thread4 : 2
Thread4 : 3
Thread4 : 4
Thread4 : 5
Thread4 : 6
Thread4 : 7
Thread4 : 8
Thread4 : 9
Thread5 : 0
Thread2 : 2
Thread2 : 3
Thread2 : 4
Thread2 : 5
Thread2 : 6
Thread2 : 7
Thread2 : 8
Thread2 : 9
Thread3 : 0
Thread1 : 1
Thread1 : 2
Thread3 : 1
Thread5 : 1
Thread9 : 0
Thread9 : 1
Thread9 : 2
Thread9 : 3
Thread9 : 4
Thread9 : 5
Thread9 : 6
Thread9 : 7
Thread9 : 8
Thread9 : 9
Thread8 : 0
Main Thread End
Thread7 : 0
Thread6 : 0
Thread0 : 5
Thread6 : 1
Thread6 : 2
Thread6 : 3
Thread6 : 4
Thread6 : 5
Thread6 : 6
Thread6 : 7
Thread6 : 8
Thread6 : 9
Thread7 : 1
Thread8 : 1
Thread8 : 2
Thread5 : 2
Thread5 : 3
Thread5 : 4
Thread5 : 5
Thread5 : 6
Thread5 : 7
Thread5 : 8
Thread5 : 9
Thread3 : 2
Thread3 : 3
Thread3 : 4
Thread1 : 3
Thread1 : 4
Thread1 : 5
Thread1 : 6
Thread3 : 5
Thread8 : 3
Thread8 : 4
Thread8 : 5
Thread8 : 6
Thread8 : 7
Thread8 : 8
Thread8 : 9
Thread7 : 2
Thread0 : 6
Thread0 : 7
Thread0 : 8
Thread0 : 9
Thread7 : 3
Thread3 : 6
Thread1 : 7
Thread3 : 7
Thread7 : 4
Thread3 : 8
Thread1 : 8
Thread3 : 9
Thread7 : 5
Thread1 : 9
Thread7 : 6
Thread7 : 7
Thread7 : 8
Thread7 : 9
위 결과를 봐도 알수 있듯이 각 Thread 가 종료되는 시점도 다르고 시작되는 시점도 다르다.
그 이유는 Thread에서의 실행순서는 예측할수 없기 때문이다.
그럼 이제 두번째 방법에 대해서 알아보자.
2. Runnable Class를 implements 받는 Class 만든다.
일단은 소스부터 살펴보자.
public class ThreadSecond implements Runnable{
private String threadName;
public ThreadSecond(String name) {
super();
this.threadName = name;
}
@Override
public void run() {
// TODO Auto-generated method stub
for(int i = 0; i<10;i++) {
System.out.println(threadName+" : " + i);
}
System.out.println();
}
}
위 소스는 Runnable class를 implements 한것이기 때문에 run() 를 꼭 오버라이딩 해야 한다.
그래서 run()를 오버라이딩 하였고 run() 안에는 thread가 시작 되었을때 할 작업을 적어놨다.
그리고 생성자로 문자열을 받아 해당 문자열로 ThreadName 으로 지정해주었다.
이렇게 만들었다면 이제 해당 Class로 Thread 를 만들어보자.
public class ThreadTest {
public static void main(String[] args) throws InterruptedException{
// TODO Auto-generated method stub
//In thread running order is not prdeictable
//Thread created by implementing Runnable
//This method is mainly used to make a thread
//In order to start() thread created by implementing Runnable class must create a Thread instance and run start()
System.out.println("Main Thread Start");
for(int i = 0; i<10;i++) {
Thread thread = new Thread(new ThreadSecond("Thread"+i));
thread.start();
}
System.out.println("Main Thread End");
}
}
위 소스도 1번 방법과 매우 유사하도록 설정을 해놨지만 다른점은 1번 방법에서는 우리가 만든 ThreadFirst class의
인스턴스를 생성한후 start()를 했지만 Runnable을 implements 했을때는 Thread class 인스턴스 객체를 만들고
해당 class의 생성인자로 Runnable class를 implments한 클래스의 객체를 집어 넣어줬다.
그렇게 함으로써 Thread의 인스턴스가 start()를 하게 되면 우리가 만든 ThreadSecond의 run()를 실행할수 있게 된다.
위 소스의 결과는 아래와 같다.
Main Thread Start
Thread0 : 0
Thread1 : 0
Thread2 : 0
Thread2 : 1
Thread2 : 2
Thread2 : 3
Thread0 : 1
Thread0 : 2
Thread7 : 0
Thread7 : 1
Thread6 : 0
Thread2 : 4
Thread2 : 5
Thread2 : 6
Thread2 : 7
Thread5 : 0
Thread5 : 1
Thread3 : 0
Thread4 : 0
Thread4 : 1
Thread1 : 1
Thread4 : 2
Thread3 : 1
Thread5 : 2
Thread2 : 8
Thread6 : 1
Thread9 : 0
Thread7 : 2
Thread7 : 3
Thread7 : 4
Thread7 : 5
Thread7 : 6
Thread7 : 7
Thread7 : 8
Thread7 : 9
Main Thread End
Thread8 : 0
Thread8 : 1
Thread8 : 2
Thread8 : 3
Thread8 : 4
Thread8 : 5
Thread8 : 6
Thread8 : 7
Thread8 : 8
Thread8 : 9
Thread0 : 3
Thread9 : 1
Thread9 : 2
Thread9 : 3
Thread9 : 4
Thread9 : 5
Thread9 : 6
Thread9 : 7
Thread9 : 8
Thread9 : 9
Thread6 : 2
Thread2 : 9
Thread5 : 3
Thread3 : 2
Thread4 : 3
Thread1 : 2
Thread4 : 4
Thread3 : 3
Thread5 : 4
Thread6 : 3
Thread0 : 4
Thread0 : 5
Thread0 : 6
Thread0 : 7
Thread0 : 8
Thread0 : 9
Thread6 : 4
Thread5 : 5
Thread3 : 4
Thread3 : 5
Thread3 : 6
Thread3 : 7
Thread3 : 8
Thread3 : 9
Thread4 : 5
Thread1 : 3
Thread4 : 6
Thread5 : 6
Thread6 : 5
Thread5 : 7
Thread4 : 7
Thread1 : 4
Thread4 : 8
Thread5 : 8
Thread6 : 6
Thread5 : 9
Thread4 : 9
Thread1 : 5
Thread6 : 7
Thread1 : 6
Thread6 : 8
Thread1 : 7
Thread6 : 9
Thread1 : 8
Thread1 : 9
1번과 2번 방법을 모두 보면은 MainThread의 종료지점이 모든 Thread가 종료되기 전에 끝나는것을 알수가 있다.
그렇게 되는 이유는 MainThread의 할일은 Thread를 생성하고 start()만 시켜주면 끝나기 때문에 해당 일을 끝나게 되면 MainThread의 작업은 종료가 된다.
하지만 나는 모든 Thread가 종료되기 전까진 MainThread를 종료시키고 싶지 않게 만들고 싶다.
이럴때 우리가 사용할수 있는것은 join() 이다.
join() 은 특정 Thread를 어떤 특정 시점까지 기다리게 할수가 있다.
소스를 통해서 한번 보자.
public class ThreadTest {
public static void main(String[] args) throws InterruptedException{
// TODO Auto-generated method stub
//'join' can be used to keep a thread waiting at a certain point.
//However , if you use 'join' InterruptedException occurs,
//So you should throw or try catch that part.
System.out.println("Main Thread Start");
Thread arr[] = new Thread[3];
for(int i = 0; i<3;i++) {
Thread thread = new Thread(new ThreadSecond("Thread"+i));
arr[i] = thread;
arr[i].start();
}
for(int i = 0; i<3;i++)
arr[i].join();
System.out.println("Main Thread End");
}
}
위 소스는 Thread를 담은 배열을 만들었고 해당 배열에 Thread를 생성하고 배열에 집어 넣고 start() 하였다.
Thread를 생성하는 MainThread에서는 start()하면 하는일이 끝나기때문에 " Main Thread End" 를 출력할수 있었지만
MainThread에서 Thread 배열안에 있는 Thread들을 모두 join() 하였기 때문에
Thread안에 있는 모든 Thread가 종료되기 전까진 Main Thread는 종료가 되지 않는다.
그래서 결과화면은 아래와 같다.
Main Thread Start
Thread0 : 0
Thread0 : 1
Thread0 : 2
Thread2 : 0
Thread2 : 1
Thread2 : 2
Thread2 : 3
Thread2 : 4
Thread2 : 5
Thread2 : 6
Thread2 : 7
Thread2 : 8
Thread2 : 9
Thread1 : 0
Thread0 : 3
Thread1 : 1
Thread0 : 4
Thread1 : 2
Thread1 : 3
Thread1 : 4
Thread1 : 5
Thread0 : 5
Thread1 : 6
Thread1 : 7
Thread1 : 8
Thread1 : 9
Thread0 : 6
Thread0 : 7
Thread0 : 8
Thread0 : 9
Main Thread End
※ join을 할때 주의할 점은 InterruptedException를 throw를 하거나 try catch를 하여야 한다.
'코딩일기 > 날씨앱 만들기 프로젝트' 카테고리의 다른 글
[Java] CompletionHandler를 이용한 Thread 콜백 구현 (0) | 2021.12.02 |
---|---|
[Java] Thread Pool 설명 및 사용 방법 (0) | 2021.11.21 |
[Spring Boot] MyBatis를 통한 MySQL 연동 (maven) (0) | 2021.11.12 |
[Spring Boot] properties 를 통해서 키 값 숨기기 (0) | 2021.10.24 |
[Spring Boot] WebClient 파라미터 인코딩 하는법 (0) | 2021.10.24 |