IT

[JAVA] Thread 개념 (single/multi)

뽀리님 2023. 12. 15. 11:14

✔ Thread?

프로세스 내에서 실행되는 여러 흐름의 단위이며 프로세스의 특정한 수행 경로이다.

즉, 프로세스가 할당받은 자원을 이용하는 실행의 단위입니다.

 

조금 이해하기 어렵다면!

프로세스(공장) , 쓰레드 (일꾼) 이라고 생각하면 이해하기 쉬울 것이다!

 

 

✔ Thread의 메모리

쓰레드는 각각 Stack 영역은 따로 할당 받고 나머지 영역은 공유한다.

따라서 하나의 쓰레드에서 오류가 발생한다면 프로세스의 다른 쓰레드도 모두 강제 종료 된다.

 

 

 

 

 멀티태스킹 & 멀티쓰레딩

  • 멀티태스킹: 여러 프로세스가 동시에 실행되는 것
  • 멀티쓰레딩: 하나의 프로세스 내에서 여러 쓰레드가 동시에 작업을 수행하는 것

 

 

 

  Single Thread

프로세스가 단일 쓰레드로 동작하는 방식이다.

하나의 레지스터, 스택으로 표현한다.

 

장점

1. 자원 접근에 대한 동기화

싱글 쓰레드의 경우 자원의 동기화를 신경쓸 필요가 없다.

 

2. 문맥 교환 (Context Switching) 작업을 하지 않음

단일 쓰레드로 동작하기 때문에 문액을 교환할 필요가 없다.

 

※ 문맥 교환이란?

CPU가 한 개의 Task를 실행하고 있는 상태에서 다른 Task로 실행이 전환되는 과정에

기존의 Task 상태 및 Register 값들에 대한 정보(문맥, Context)를 저장하고,

새로운 Task의 정보(문맥, Context)으로 교체하는 작업

 

※ 레지스터란? CPU가 요청을 처리하는데 필요한 데이터를 일시적으로 저장하는 기억장치이다.

 

단점

1. CPU 코어를 모두 활용하지 못함.

싱글 스레드는 하나의 물리적 코어밖에 사용하지 못해 멀티 코어 머신에서 CPU 사용을 최적화할 수 없다.

최적화를 위해선 Cluster 모듈을 이용하여 여러 프로세스를 사용할 수 있다.

하지만 앞서 프로세스끼리의 자원 공유는 어렵기 때문에 Redis와 같은 부가 인프라가 필요하다.

 

 

 

Multi Thread

두개 이상의 쓰레드가 프로세스 내부에서 자원을 공유하여 작업을 수행한다.

각각 쓰레드가 고유 레지스터와 스택으로 표현된다.

 

장점

1. CPU 사용률 향상

싱글스레드와 달리 다중 CPU 구조에서 각각의 스레드가 다른 프로세서에서 병렬로 수행될 수 있습니다.

 

2. 작업이 분리되기 때문에 코드가 간결해진다.

3. 사용자에 대한 응답성 향상

4. 작업이 분리되기 때문에 코드가 간결해진다.

 

 

단점

1. 교착 상태

두 개 이상의 작업이 하나씩 자원을 소지하고 있으면서 상대방이 가진 자원을 서로 원하고 있는 상태.어떤 작업도 실행되지 못하고 계속 서로 상대방의 작업이 끝나기만을 바라는 무한정 대기상태이다.

 

2. 동기화 문제

둘 이상의 쓰레드에서 공유 자원에 접근 할 때 와  다른 쓰레드에서 사용 중인 경우 값을 읽어 올 때 동기화 문제가 발생할 수 있다.

 

※  그럼 동기화는 하지 못하는가?

아니다. 자바의 synchronized와 같이 동기화를 할 수 있다.

이때 synchronized 키워드를 사용한 메소드 혹은 블럭은 보호 구역으로 지정되며 하나의 스레드만 자원을 사용할 있도록 Lock 걸어준다.

 

 

 

 

Thread 구현 실행

Thread 클래스를 상속받으면 다른 클래스를 상속받을 수 없다.

때문에 Runnuable 인터페이스로 구현하는 것이 일반적이다.

 

Runnable로 구현하는 방법은 재사용성이 높고 코드의 일관성을 유지할 수 있기 때문에 보다 객체지향적인 방법이라고 할 수 있다.


Runnable로 구현할 경우,

Thread 클래스의 static메서드인 currentThread를 호출하여 쓰레드에 대한 참조를 얻어 와야만 호출 가능하다.

 

반대로 Thread 상속하여 구현하는 경우는 Thread 클래스의 메서드를 직접 호출 가능하다.

 

public class Test {
    public static void main(String args[]) {
        ThreadEx1_1 t1 = new ThreadEx1_1();

        Runnable r  = new ThreadEx1_2();
        Thread t2 = new Thread(r);    // 생성자 Thread(Runnable target)
        // Thread t2 = new Thread(new ThreadEx1_2())
        t1.start(); //쓰레드 실행
        t2.start(); //쓰레드 실행
    }
}

//Thread 클래스를 상속할 경우
class ThreadEx1_1 extends Thread {
    public void run() {
        for(int i=0; i < 5; i++) {
            //getName를 직접 호출 가능
            System.out.println(getName()); // 조상인 Thread의 getName()을 호출
        }
    }
}

class ThreadEx1_2 implements Runnable {
    public void run() {
        for(int i=0; i < 5; i++) {
            // Thread.currentThread() - 현재 실행중인 Thread를 반환한다.
            System.out.println(Thread.currentThread().getName());
        }
    }
}

 

Thread 클래스를 상속하여 구현한 코드를 보면 getName 메소드를 직접 호출한다.

반면, Runnable 인터페이스로 구현한 것은 직접 호출하지 못하고 현재 실행중인 Thread를 가져와서 getName 메소드를 호출한다.

 

그리고 쓰레드 호출에 있어서 알아야 할 부분들이 있다.

 

start() 새로운 쓰레드를 생성하고, 쓰레드가 작업하는데 사용될 호출스택을 생성

start()를 호출한다해서 쓰레드가 시작되는 것이 아니라 실행 대기 상태에 있다가 차례가 되면 실행한다.
한 번 start하면 재할당을 하지 않으면 재호출 불가능하다.(한번만 호출가능하다)

즉, 종료된 쓰레드는 다시 실행 할 수 없다.

 

만일 하나의 쓰레드에 대해 start() 이상 호출하면 실행시 IllegalThreadStateException 에러가 발생한다.

 

 

 

 

Main Thread

프로세스는 각각 독립된 메모리 영역 (Code, Data, Stack, Hea) 구조를 할당 받고최소 1개의 메인 Thread 가지고 있다.

 

main메서드의 작업을 수행하는 것도 쓰레드이며, 이를 main쓰레드라고 한다.

우리는 지금까지 우리도 모르는 사이에 이미 쓰레드를 사용하고 있었던 것이다!!

 

앞에서 쓰레드를 쉽게 말해서 일꾼이라고 하였는데,

프로그램이 실행되기 위해서 작업을 수행하는 일꾼이 최소한 하나는 필요하지 않을까?

그래서 프로그램을 실행하면 기본적으로 하나의 쓰레드를 생성하고, 그 쓰레드가 main메서드를 호출해서 작업이 수행되도록 하는 것이다.

 

main쓰레드는 실행 중인 사용자 쓰레드가 하나도 없을 프로그램은 종료된다.

 

 

 

 

그럼 SingleThread 와 MultiThread 중에 어떤걸 사용해야 할까?...

참조 https://dev-cini.tistory.com/84

 

위 그림을 보면,

하나의 쓰레드로 두 개의 작업을 수행하는 경우

두 개의 쓰레드로 두 개의 작업을 수행하는 경우 의 그림이 있다.

 

둘 중 어떤 쓰레드가 작업 속도가 더 빠를까?

상황에 따라 다르다!

 

싱글코어에서는 단순히 CPU만을 사용하는 계산작업은 싱글 쓰레드가 더 효율적이다.

 

이유는 뭘까?

멀티 쓰레드는 두개의 쓰레드가 작업을 번갈아가면서 처리한다.그러면 한 쓰레드가 작업이 끝나고 나서 다른 쓰레드로 작업을 전환한다.

작업을 전활  다음에 실행해야할 위치 등의 정보를 저장하고 읽어 오는 시간 소요된다.

 

 

그러면 두 쓰레드가 서로 다른 자원을 사용할 경우에는 어떨까?

 

멀티쓰레드가 효율적이다.

다른 쓰레드가 입력을 기다릴때 또 다른 쓰레드가 일을 처리하면 되기 때문이다.

ex) 사용자로부터 데이터를 입력받는 작업, 네트워크로 파일을 주고받는 작업, 프린터로 파일을 출력하는 작업과 같이 외부기기와의 입출력이 필요로 하는 경우가 이에 해당한다.

 

 

참조 

https://dev-cini.tistory.com/84