개발 & 데이터베이스/JAVA

[JAVA] 자바 스레드 (Thread) 개념과 사용 방법 2가지 (start, join 메소드)

K.두부 2022. 8. 9. 20:20
반응형

안녕하세요. 두부입니다.

오늘은 스레드 (Thread)에 대해서 알아보겠습니다.

 

스레드 (Thread) 개념

하나의 프로세스 (실행 중인 프로그램)에서 독립적으로 실행되는 작업의 단위를 말함. 

모든 프로세스에는 한 개 이상의 쓰레드가 존재하며, 두 개 이상의 쓰레드를 가지는 프로세스를 멀티쓰레드 프로세스라고 부름.

 

장점

가장 큰 장점은 동시성으로 한 번에 여러 일들을 할 수 있기 때문에 작업의 효율성이 높아짐

쓰레드끼리 메모리를 공유하기 때문에 메모리가 절약됨

 

단점

서로 자원을 소모하다가 충돌이 일어날 가능성이 존재함 (교착상태 or 기아상태)

프로그램이 상당히 복잡해질 수 있음

 

스레드의 생성 주기와 생명 주기

생성 주기

Runnable 상태 : 스레드가 실행되기위한 준비 단계

Running 상태 : 스케줄러에 의해 선택된 스레드가 실행되는 단계

Blocked 상태 : 스레드가 작업을 완수하지 못하고 잠시 작업을 멈추는 단계

 

생명 주기

1. Runnable (준비 상태) 

스레드가 실행되기 위한 준비단계로 CPU를 점유하고 있지않으며 실행 (Running 상태)을 하기 위해 대기하고 있는 상태. 코딩 상에서 start() 메소드를 호출하면 run() 메소드에 설정된 스레드가 Runnable 상태로 진입.

 

2. Running (실행 상태)

CPU를 점유하여 실행하고 있는 상태로 run() 메소드는 JVM만이 호출 가능함. Runnable (준비 상태)에 있는 여러 스레드 중 우선 순위를 가진 스레드가 결정되면 JVM이 자동으로 run() 메소드를 호출하여 스레드가 Running 상태로 진입

 

3. Dead (종료 상태)

Running 상태에서 스레드가 모두 실행되고 난 후 완료 상태. 

 

4. Blocked (지연 상태)

CPU 점유권을 상실한 상태로 특정 메소드를 실행시켜 Runnable (준비 상태)로 전환함. wait() 메소드에 의해 Blocked 상태가 된 스레드는 notify() 메소드가 호출되면 Runnable 상태로 감. sleep() 메소드에 의해 Blocked 상태가 된 스레드는 지정된 시간이 지나면 Runnable 상태로 감.

 

스레드 사용하는 방법

자바에서 스레드를 사용하는 방법은 크게 두 가지가 있습니다. 

 

1. Thread 클래스를 상속받는 방법

2. Runnable 인터페이스를 구현하는 방법

 

1. Thread 클래스를 상속받는 방법

class MyThread extends Thread{
    public MyThread(String threadName){
        super(threadName);
    }
	
    public void run(){
        for(int i=0;i<50;i++){
            System.out.println(this.getName()+":"+i);
        }
        System.out.println();
    }
}

public class ThreadTest {
    public static void main(String[] ar){
        System.out.println("MainThread Start");
        for(int i=0;i<3;i++){
            new MyThread("Thread"+i).start();
        }
        System.out.println("MainThread End");	
    }
}
MainThread Start
MainThread End
Thread1:0
Thread3:0
Thread1:1
Thread1:2
Thread2:0
Thread1:3
···
Thread3:25
Thread1:31 
···
Thread2:48
Thread2:49

일반적인 프로그램이였다면 순차적으로 Thread1:0 부터 Thread3:49까지 실행되었겠지만 예상과 다르게 뒤죽박죽으로 실행됩니다. 멀티스레드의 동시성 때문에 발생하는 현상으로 실행 순서는 예측할 수가 없습니다.

 

2. Runnable 인터페이스 구현

스레드를 생성할 때 주로 사용하는 방법입니다.

class MyThread implements Runnable{
    private String threadName;
    public MyThread(String threadName){
        this.threadName=threadName;
    }
    
    public void run(){
        for(int i=0;i<50;i++){
            System.out.println(threadName+":"+i);
        }
    }
}

public class ThreadTest {
    public static void main(String[] ar){
        System.out.println("MainThread Start");
        for(int i=0;i<3;i++){
            Thread thread=new Thread(new MyThread("Thread"+i));
            thread.start();
        }
        System.out.println("MainThread End");	
    }
}
MainThread Start
MainThread End
Thread0:0
Thread0:1
Thread1:0
Thread2:0
···
Thread0:12
Thread2:21
Thread0:13
···
Thread2:48
Thread2:49

1번 방법과 동일하게 뒤죽박죽으로 실행되며 순서를 예측할 수 없습니다.

 

위 결과 화면을 보시면 두 가지 방법 모두 MainThread가 먼저 끝나버리고 있는데요. MainThread를 마지막에 종료할 수 있도록 해보겠습니다. 우선 순서를 변경하려면 join 메소드를 이용해야하는데 이 메소드를 사용하게 되면 interruptedException이 발생합니다. 예시를 보여주기 위해서 throws 해놓고 실행해보겠습니다.

class MyThread implements Runnable{
    private String threadName;
    public MyThread(String threadName){
        this.threadName=threadName;
    }
	
    public void run(){
        for(int i=0;i<50;i++){
            System.out.println(threadName+":"+i);
        }
    }
}

public class ThreadTest {
    public static void main(String[] ar) throws InterruptedException{
        System.out.println("MainThread Start");
        Thread[] thread=new Thread[4];
        for(int i=0;i<3;i++){
            thread[i]=new Thread(new MyThread("Thread"+i));
            thread[i].start();
        }
		
        for(int i=0;i<3;i++){
            thread[i].join();
        }
        System.out.println("MainThread End");	
	}
}
MainThread Start
Thread0:0
Thread0:1
Thread0:2
···
Thread1:20
Thread0:26
···
Thread2:48
Thread2:49
MainThread End

 

반응형