CHAPTER 19. Thread 알아가기
오늘은 Java에서 특정한 Task를 돌릴 때 동시에 여러 일을 수행할 수 있게 해주는 Thread에 대해 포스팅해보려 합니다!
1 ) 프로세스(Process)와 스레드(Thread)
- 프로세스(Process) : 실행 중인 프로그램(운영체제로부터 자원을 할당받는 작업의 단위)라고 할 수 있습니다.
즉, 사용자가 작성한 프로그램이 운영체제에 의해 메모리 공간을 할당받아 실행 중인 것을 말합니다.
- 스레드(Thread) : 프로세스 내에서 할당받은 자원을 이용하는 실행의 단위입니다. 모든 프로세스에는 한 개 이상의 스레드가 존재하여 작업을 수행하며, 두 개 이상의 스레드를 가지는 프로세스를 멀티스레드 프로세스(multi-threaded process)라고 합니다.
예를 들면 운영체제(OS)에서 실행 중인 하나의 애플리케이션 즉, 작업 관리자에서 프로세스 탭에 올라와 있는 애플리케이션 하나를 하나의 프로세스라고 부르는데, 만약 우리가 크롬을 2개 띄웄다면 두 개의 프로세스가 생성된 것입니다.
2 ) 멀티 태스킹(Multi Tasking)과 멀티 스레드(Multi Thread)
멀티 태스킹이란 두 가지 이상의 작업을 동시에 처리하는 것을 말합니다. 예를 들어 워드로 문서작업을 하는 동시에 음악을 듣는 것은 OS가 프로세스마다 작업을 병렬로 처리하기에 가능합니다.
또한 멀티 태스킹이 꼭 멀티 프로세스(워드 + 곰플레이어 프로세스 조합)를 말하는 것은 아닌데, 한 프로세스 내에서도 멀티 태스킹을 할 수 있도록 만들어진 예를 보면 카톡, 메신저 프로세스 같은 경우 채팅 기능을 제공하면서 동시에 파일 업로드 기능을 수행할 수 있습니다.
이처럼 한 프로세스에서 멀티 태스킹이 가능한 이유는 멀티 스레드 덕분이라고 보시면 됩니다.
멀티 프로세스는 프로세스마다 운영체제로부터 할당받은 고유의 메모리를 서로 침범할 수 없지만 멀티 스레드는 프로세스 내부에서의 스레드들끼리 공유되는 자원이 있어서 하나의 스레드에서 예외가 발생한다면 프로레스 자체가 종료될 수 있습니다.
2-1 ) Multi Thread를 사용해야 하는 경우
- GUI 프로그래밍에서는 main Thread에서만 UI를 그리거나 갱신할 수 있습니다.
- 시간이 오래 걸리는 작업의 경우 ANR(Application not Responding) 현상을 방지하기 위해 백 그라운드에서 실행되는 별도의 Thread가 필요합니다.
- 만약 그렇지 않으면 화면을 갱신하고자 하는 모든 코드는 block 당하여 ANR(Application not Responding)이 발생하게 됩니다.
2-2 ) Multi Thread의 장단점
- 장점
- 자원을 보다 효율적으로 사용할 수 있습니다.
- 사용자에 대한 응답성(responseness)이 향상됩니다.
- 작업이 분리되어 코드가 간결해 집니다.
- 단점
- 동기화(synchronization)에 주의해야 합니다.
- 교착상태(dead-lock)가 발생하지 않도록 주의해야 합니다.
- 각 Thread가 효율적으로 고르게 실행될 수 있게 해야 합니다.
"프로그래밍할 때 고려해야 할 사항들이 많습니다."
3 ) Thread의 상태제어
위 사진처럼 Thread가 3개가 있다면 JVM은 시간을 잘게 쪼갠 후에 한 번은 Thread1을, 한 번은 Thread2를, 한번은 Thread3을 실행합니다. 이것이 빠르게 일어나다 보니 Thread가 모두 동작하는 것처럼 보입니다.
4 ) Thread 구조
- Thread는 실행가능 상태인 Runnable과 실행 상태인 Running 상태로 나뉩니다.
- 실행되는 Thread 안에서 Thread.sleep()이나 Object가 가지고 있는 wait() 메서드가 호출이 되면 Thread는 블록 상태가 됩니다.
- Thread.sleep()은 특정시간이 지나면 자신 스스로 블록상태에서 빠져나와 Runnable이나 Running 상태가 됩니다.
- Object가 가지고 있는 wait() 메서드는 다른 Thread가 notify()나 notifyAll() 메서드를 호출하기 전에는 블록상태에서 해제되지 않습니다. (그래서 대기 중인 다른 메서드가 실행하게 됩니다.)
- Thread의 run 메서드가 종료되면, Thread는 종료됩니다.
- Thread의 yeild() 메서드가 호출되면 해당 Thread는 다른 Thread에게 자원을 양보하게 됩니다.
- Thread가 가지고 있는 join() 메서드를 호출하게 되면 해당 Thread가 종료될 때까지 대기하게 됩니다.
5 ) Thread 구현과 실행
자바에서 Thread를 생성하는 방법에는 다음과 같이 두 가지 방법이 있습니다.
- Runnable 인터페이스를 구현하는 방법
- Thread 클래스를 상속받는 방법
두 방법 모두 스레드를 통해 작업하고 싶은 내용을 run() 메서드에 작성하면 됩니다.
다음 예제는 위의 두 가지 방법 중 스레드를 생성하고 실행하는 예제입니다.
//멀티스레드 : 작업단위가 2개 이상
//멀티스레드 구현하는 방법
//1. Thread를 상속
public class ThreadExam extends Thread{
public ThreadExam(String name){
super(name);//부모생성자 호출 - 타이틀 설정
}
@Override // 반드시 오버라이드
public void run() {// 스레드 실행 메소드
for (int i=1; i<=5; i++){
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(1000); // Cpu를 1초간 멈춤
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public static void main(String[] args) {
ThreadExam e1 = new ThreadExam("thread1");
ThreadExam e2 = new ThreadExam("thread2");
ThreadExam e3 = new ThreadExam("thread3");
//e1.run()을 호출하면 main Thread가 호출됨
e1.start();// 스레드객체.start() =>run()이 호출됨
e2.start();// 위와 동시에 호출
e3.start();// 위와 동시에 호출
}
}
위 예제의 실행 결과를 살펴보면, 생성된 Thread가 서로 번갈아가며 실행되고 있는 것을 확인할 수 있습니다.
또한, 순서대로 출력이 안되며, cpu가 마음대로 출력해서 보내는 걸 알 수 있는데 Thread는 자바에서 우선순위를 관여해서 자신만의 필드를 생성할 수 있습니다.
아래 예제는 Runnable 인터페이스를 구현하는 방법입니다.
public class RunnableExam implements Runnable{
@Override
public void run() {
for (int i=1; i<=100; i++){
System.out.println(Thread.currentThread().getName()+"==>"+i);
}
}// end run()
public static void main(String[] args) {
RunnableExam e1 = new RunnableExam();
// Runnabla을 쓸 때는 Thread를 별도로 생성해서 써야한다.
// Java는 단일 상속만을 하기 때문에 다른 객체와 함께 상속받아 스레드를 구현하려면
// implements Runnable로 처리
// new Thread(스레드구현객체, "스레드이름")
Thread t1 = new Thread(e1, "스레드1");
Thread t2 = new Thread(e1, "스레드2");
t1.start();//run()호출
t2.start();
// t1.run()을 하면 main인 싱글스레드가 돌아간다.
// t1.run();
// t2.run();
}
}
위 예제들을 실행 결과를 살펴보면, Thread 클래스를 상속받으면 다른 클래스를 상속받을 수 없으므로, 일반적으로 Runnable 인터페이스를 구현하는 방법으로 Thread를 생성합니다.
※ Runnable 인터페이스는 몸체가 없는 메서드인 run() 메서드 단 하나만을 가지는 간단한 인터페이스입니다.
6 ) Thread의 우선순위
위 예제들처럼 cpu가 마음대로 출력하기 때문에 자바에서는 각 Thread에 우선순위(priority)에 관한 자신만의 필드를 가지고 있습니다.
이러한 우선순위에 따라 특정 스레드가 더 많은 시간 동안 작업을 할 수 있도록 설정할 수 있습니다.
필 드 | 설 명 |
NORM_PRIORITY | Thread가 생성될 때 가지는 기본 우선순위를 명시합니다. |
MIN_PRIORITY | Thread가 가질 수 있는 최소 우선순위를 명시합니다. |
MAX_PRIORITY | Thread가 가질 수 있는 최대 우선순위를 명시합니다. |
getPriority(), setPriority() setPriority() 메서드를 통해 Thread의 우선순위를 반환하거나 변경할 수 있습니다.
Thread의 우선순위가 가질 수 있는 범위는 1~10까지 이며, 숫자가 높을수록 우선순위 또한 높아집니다.
하지만 우선순위는 비례적인 절댓값이 아닌 어디까지나 상대적인 값일 뿐입니다.
우선순위가 10인 Thread가 우선순위가 1인 Thread 보다 더 빨리 수행되는 것은 아닙니다. 단지 우선순위가 10인 Thread는 우선순위가 1인 Thread보다 좀 더 많이 실행 큐에 포함되어, 좀 더 많은 작업 시간을 할당받을 뿐입니다.
다음 예제로 getPriority() , setPriority()setPriority() 메서드를 통해 Thread의 우선순위를 알아봅시다.
public class Priority extends Thread{
@Override
public void run() {
for (int i=1; i<=10; i++){
System.out.println(Thread.currentThread().getName()+"==>"+i);
}
}
public static void main(String[] args) {
Priority e1 = new Priority();
Priority e2 = new Priority();
//스레드 이름 설정
e1.setName("스레드1");
e2.setName("스레드2");
System.out.println("e1의 기본 우선순위 : "+ e1.getPriority());
System.out.println("e2의 기본 우선순위 : "+ e2.getPriority());
//e1.setPriority(7);//숫자로도 줄 수 있으나 상수값으로 해야 그나마 cpu에 잘 반영됨
e1.setPriority(Thread.MIN_PRIORITY);//최소 우선순위(1)
e2.setPriority(Thread.MAX_PRIORITY);//최대 우선순위(10)
e1.start();
e2.start();
}
}
위 예제 실행 결과를 보면 우선순위를 먼저 정한 스레드 2가 다 끝나고 실행되는 게 아닌, 좀 더 실행큐에 포함되어, 좀 더 많은 작업시간을 할당받는 걸 알 수 있습니다.
7 ) synchronized
synchronized 키워드는 한 번에 하나의 스레드만 객체에 접근할 수 있도록 객체에 락(lock)을 걸어서 데이터의 일관성을 유지할 수 있게 해 줍니다.
그럼 예제 코드를 통해서 알아봅시다. Thread를 활용하여 MusicBox 플레이를 해보았습니다.
public class MusicBox { // 공유객체
//모니터링 락(객체의 사용권)
public synchronized void playMusicA(){
for (int i=0; i<10; i++){
System.out.println("가요 음악!!!");
try {
Thread.sleep((int)(Math.random()*1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void playMusicB(){
for (int i=0; i<10; i++){
System.out.println("pop 음악!!!");
try {
Thread.sleep((int)(Math.random()*1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void playMusicC(){
for (int i=0; i<10; i++){
//메소드의 코드가 길어지면, 마지막에 대기하는 스레드가 너무 오래 기다리는 것을 막기 위해서
//메소드 헤드에 synchronized를 처리 안하고 필요한 부분만 synchronized 블록을 사용할 수 있다.
synchronized (this){//(this)는 MusicBox객체 자신을 가리킴
System.out.println("클래식 음악!!!");
}
try {
Thread.sleep((int)(Math.random()*1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class MusicPlayer extends Thread {
int type;
MusicBox musicBox;
public MusicPlayer(int type, MusicBox musicBox){
this.type = type;
this.musicBox = musicBox;
}
@Override
public void run() {
switch (type){
case 1: musicBox.playMusicA();break;
case 2: musicBox.playMusicB();break;
case 3: musicBox.playMusicC();break;
}
}
}
public class MusicBoxExam1 {
public static void main(String[] args) {
MusicBox box = new MusicBox();
MusicPlayer kim = new MusicPlayer(1,box);
MusicPlayer lee = new MusicPlayer(2,box);
MusicPlayer park = new MusicPlayer(3,box);
// 스레드 실행
kim.start();
lee.start();
park.start();
}
}
위 예제 코드를 실행결과 한 스레드가 수행할 때 다른 스레드에 의해 간섭을 받이 않는 걸 알 수 있습니다.
8 ) 데몬 스레드(Daemon Thread)
데몬 스레드는 메인 스레드의 작업을 돋는 보조적인 역할을 수행하는 스레드입니다. 주 스레드가 종료되면 데몬 스레드는 더는 존재 의미가 없기에 강제로 종료시킵니다. 워드의 자동 저장 기능을 예로 들을 수 있는데, 데몬 스레드를 만드는 방법은 스레드를 만들고 해당 스레드에 setDaemon(true); 메서드를 세팅합니다.
그럼 예제 코드로 알아봅시다.
public class DaemonThread implements Runnable{
@Override
public void run() {
while (true){ // while(true)지만 main 스레드가 종료되면 자동 종료됨.
System.out.println("데몬 스레드가 실행중입니다.");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
break;//Exception 발생시 while문 빠져나가도록
}
}//while문
}//run
public static void main(String[] args) {
Thread th = new Thread(new DaemonThread());
th.setDaemon(true);//데몬스레드 설정
th.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("메인 스레드가 종료됩니다.");
}
}
위 예제 출력결과를 보면, while(true)지만 , main 스레드가 종료되면서 자동으로 종료되는 걸 알 수 있습니다.
마치며
기본적으로 Thread 구현하는 방법 및 개념을 정리해 보았습니다. 만약에 자원을 동시에 사용하거나 순서의 제어가 필요한 경우에는 동시성 처리를 해줘야 할 필요가 있습니다!!
'[ JAVA ] > JAVA' 카테고리의 다른 글
[ Java ] GUI 프로그래밍 응용 (2) | 2023.01.09 |
---|---|
[ Java ] GUI 프로그래밍 개념 (2) | 2023.01.07 |
[ Java ] 예외처리(Exception Handling) (0) | 2023.01.04 |
[ Java ] 내부 클래스(Inner Class) (0) | 2023.01.03 |
[ Java ] Scanner (0) | 2022.12.29 |