본문 바로가기
Language/JAVA

쓰레드(Thread) 는 무엇일까?

by YoonJong 2023. 2. 12.
728x90

프로세스는 프로그램을 실행 한 것을 의미합니다.

프로세스의 자원을 이용해서 실제로 작업을 수행하는 것을 쓰레드 라고 합니다.

 

모든 프로세스에는 1개 이상의 쓰레드가 존재하며, 둘 이상의 쓰레드를 가진 프로세스를 멀티쓰레드 프로세스라고 합니다.

 

멀티 쓰레드를 사용하면 하나의 프로세스에서 여러가지 작업을 동시에 진행할 수 있어 효율적으로 사용할 수 있습니다.

자바는 멀티쓰레드를 지원하므로, 고비용의 프로세스를 추가시키는 보다 멀티 쓰레드를 이용할 수 있습니다.

 

멀티쓰레드의 장점은 아래와 같습니다.

1. CPU의 사용율을 향상시킨다

2. 자원을 효율적으로 사용할 수 있다.

3. 사용자에 대한 응답성이 향상된다. ex) 채팅프로그램에서 파일전송을 할때 보내면서 채팅을 할 수 있다.

4. 작업이 분리되어 코드가 간결해진다.

 

단점은 공유하는 것에서 문제가 발생할 수 있습니다.

1. 동기화

2. 교착상태 -> 쓰레드 서로가 서로의 자원을 달라고 하는 상태


쓰레드를 구현하는 방법은 Thread 클래스를 상속받는 방법과 Runnable 인터페이스를 구현하는 방법이 있습니다.

상속은 단일상속만 지원하기 때문에, 인터페이스를 구현하는 것이 일반적인 방법입니다.

public class Thread implements Runnable {
    @Override
    public void run() {
      // 쓰레드가 수행할 작업 작성 
    }
}

싱글쓰레드와 멀티쓰레드의 차이를 알아보겠습니다.

먼저 멀티쓰레드의 결과를 보면 아래와 같습니다.

 

쓰레드를 생성하고 나서 start() 메서드를 호출해야 작업이 시작됩니다.

start() 를 호출하면, 새로운 Call Stack 이 생기면서 또 다른 작업이 진행됩니다.

호출하지않고 구현한 run() 을 호출한다면 main 스택 위에 그대로 쌓여버리기 때문에 싱글스레드와 같습니다.

 

다만 thread1.start() 를 먼저 진행한다고해서, 먼저 실행된다는 말이 아니며, 실행준비 상태로 변환됩니다.

어떤 쓰레드를 실행할지는 OS의 스케줄러가 판단합니다.

자바 JVM 이 OS 에 독립적이지만, 쓰레드는 OS 에 종속적입니다.

public class MultiTreadTest {
    public static void main(String[] args) {

        ThreadEx1_1 t1 = new ThreadEx1_1();
        Runnable r = new ThreadEx1_2();
        Thread thread1 = new Thread(t1);
        Thread thread2 = new Thread(r);

        thread1.start();
        thread2.start();
    }
}

class ThreadEx1_1 extends Thread {
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.print(1);
        }
    }
}

class ThreadEx1_2 implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.print(2);
        }
    }
}

// 결과값
111122222222222222211222222222211111111111111111111111111111111111111111111111
111111111111111122222222211111111111111111111111111222222222222221111122222222222
22222222222222222222222222222222222222222

 

싱글스레드로 테스트 해보겠습니다.

public class Ex13_1 {
    public static void main(String[] args) {

        for (int i = 0; i < 100; i++) {
            System.out.print(1);
        }

        for (int i = 0; i < 100; i++) {
            System.out.print(2);
        }

    }
}

// 결과값
111111111111111111111111111111111111111111111111111111111111111111111
111111111111111111111111111111122222222222222222222222222222222222222
22222222222222222222222222222222222222222222222222222222222222

 

결과값의 차이를 보면 멀티쓰레드는 쓰레드가 2개이상 이기 때문에 섞여있는 것을 볼 수 있고,

싱글스레드는 위에서부터 작업을 차례대로 진행하므로 절대 섞일 수가 없습니다.


main 쓰레드는 종료되어도 멀티 쓰레드 환경에서는 나머지 쓰레드가 종료될 때 까지 프로그램이 종료되지 않습니다.

join() 메서드는 기다리는 역할을 합니다.

주석처리를 했을 경우, 소요시간이 먼저 출력되면서 main 쓰레드가 종료되고 이후 thread1, thread2 가 실행됩니다.

public class ThreadTest {
    public static void main(String[] args) {

        ThreadEx1_1 t1 = new ThreadEx1_1();
        Runnable r = new ThreadEx1_2();
        Thread thread1 = new Thread(t1);
        Thread thread2 = new Thread(r);

        thread1.start(); // main 쓰레드가 thread1의 작업이 끝날 때 까지 기다린다.
        thread2.start(); // main 쓰레드가 thread2의 작업이 끝날 때 까지 기다린다.
        long startTime = System.currentTimeMillis();

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {}

        System.out.println("소요시간 : " + (System.currentTimeMillis() - startTime));

    }
}

class ThreadEx1_1 extends Thread {
    public void run() {
        for (int i = 0; i < 300; i++) {
            System.out.print(new String("-"));
        }
    }
}

class ThreadEx1_2 implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 300; i++) {
            System.out.print(new String("|"));
        }
    }
}

// 결과값
||||||||||||||||||||||||||--------------|||--|||||||||||||||||||--------
|||||||||||--------||||||||||||||||||-----||||||-------------------------
------------|||||||||||||||||||||||||||||||||||||||---------------------
-------------------------------------|||||||||||||||||||||||||||||||||||||
|||||||||||||||||||||||||||||||||-----------------------------------||---
||||--------------|||---------|||||||||---||||---||----------------------
--------|||-------|-----|||||||||||||||||||||||||||||||||||||||||||||||||-
------|||||||||||||||||||||||||||||||------------------------------------
----------------소요시간 : 6

싱글 쓰레드와 멀티 쓰레드의 작업 실행시간은 차이가 있습니다.

멀티 쓰레드는 번걸아가면서 작업을할때 컨텍스트 스위칭(쓰레드 간 작업 전환)이 발생하기 때문입니다.

시간은 조금 더 걸리지만, 예를 들어 채팅할 때 파일을 보내는 동안 동시에 채팅을 하는 등 이러한 작업을 하기 위해서 

멀티 쓰레드를 사용합니다.

 

단순히, CPU 만을 사용하는 단순한 작업이면 싱글스레드를 사용하는 것이 효율적일수 있습니다.


쓰레드는 우선순위를 정할 수 있습니다. 기본 디폴트값은 5 입니다.

1~10 까지의 숫자를 사용할 수 있으며 높을 수록 우선순위가 높아집니다.

사용법은 아래왜 같이 setpriority( ) 를 사용해서 설정할 수 있습니다.

하지만, 이것은 절대적인 것은 아니며, OS의 스케줄러에게 권한이 있으므로 희망사항일 뿐입니다.

thread1.setPriority(1);
thread2.setPriority(10);

데몬 쓰레드는 일반 쓰레드의 작업을 도와주는 역할을 합니다.

예를들어 가비지 컬렉터, 워드프로세서의 자동저장 등의 보조작업이 있습니다.

설정 방법은 setDaemon( ) 을 사용하며, 꼭 쓰레드가 start() 되기 전에 설정해주어야 합니다.

thread1.setDaemon(true);
thread1.start();

데몬 쓰레드의 무한루프와 조건문으로 작성하며, 아래 예제를 보겠습니다. 

설정할때 가장 중요한 것은 setDeamon을 꼭 설정해주어야 하는 것입니다.

만약 설정하지 않으면, 쓰레드가 종료되지 않기 때문에 강제적으로 프로그램을 종료시켜주어야 합니다.

public class DemonThreadTest {

    public static boolean autoSave = false;

    public static void main(String[] args) {
        Demon demon = new Demon();
        Thread thread = new Thread(demon);
        thread.setDaemon(true); // 설정하지 않으면 종료되지 않는다.
        thread.start();

        for (int i = 1; i <= 10; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {}

            System.out.println(i);

            if ( i == 5 ) {
                autoSave = true;
            }
        }
        System.out.println("프로그램이 종료되었습니다.");
    }

    static class Demon implements Runnable {
        @Override
        public void run() {
            while (true) {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                }

                if (autoSave) {
                    autoSave();
                }
            }
        }
    }

    public static void autoSave() {
        System.out.println("파일이 저장되었습니다.");
    }
}

//결과값
1
2
3
4
5
파일이 저장되었습니다.
6
7
8
파일이 저장되었습니다.
9
10
프로그램이 종료되었습니다.

쓰레드의 상태는 아래와 같습니다.

 

순서는 아래와 같이 진행됩니다.

1. 쓰레드를 생성하고 start() 를 호출하면 바로 실행되는 것이 아닌, 실행대기열에 저장됩니다.

  실행대기열은 큐 구조 ( FIFO ) 로 되어있으며, 순서대로 진행됩니다.

2. 자신의 차례가 되면 실행되고, 주어진 시간이 다되거나 , yield() (양보) 를 만나면 다시 실행대기 상태가 되고 다음 차례의 쓰레드가 실행됩니다.

3. 입출력에서 발생하는 지연시상태인 I/O block 에 의해 일시정지 상태가 된다면, 해당 작업이 끝난 후 다시 실행대기 상태가 됩니다.

4. 실행을 모두 마치면 종료됩니다.

728x90

'Language > JAVA' 카테고리의 다른 글

람다식과 함수형 인터페이스  (0) 2023.02.13
쓰레드 동기화(Synchronization)  (0) 2023.02.13
Thread.sleep() 과 interrupted()  (0) 2023.02.12
열거형(Enum) 을 알아보자  (0) 2023.02.12
Generics (지네릭스) 를 알아보자  (0) 2023.02.12

댓글