JAVA

JAVA 쓰레드 1

woohap 2025. 2. 11. 01:38

프로세스와 쓰레드

- 프로세스란 현재 실행 중인 프로그램을 의미한다. 
  프로그램을 실행하면 운영체제로부터 실행에 필요한 자원을 할당 받아 프로세스가 됨 
- 프로세스는 실행에 필요한 데이터, 자원, 쓰레드 등으로 구성되어 있다.
- 프로세스는 적어도 하나 이상의 쓰레드가 존재한다.
  둘 이상의 쓰레드를 가진 프로세스를 멀티 쓰레드 프로세스라고 한다.
- 쓰레드 생성 제한이 없지만, 쓰레드 또한 메모리 공간을 필요로 하기 때문에 
  프로세스의 메모리 한계에 따라 생성할 수 있는 쓰레드 수가 결정된다.
- 개발자가 직접 쓰레드 개수 조정 가능 

프로세스 성능은 스레드의 수에 비례하지 않음

- 하나의 코어는 한 번에 하나의 작업을 수행한다.
  즉, 여러 쓰레드가 한 코어를 번갈아 수행하는 것이기에 
  쓰레드가 많다고 무조건 성능이 많이 올라가는 것이 아니다.

멀티 쓰레드 장점

- CPU 사용률을 향상 시킨다. 
- 자원을 보다 효율적으로 사용할 수 있다.
  스레드들은 프로세스의 자원을 공유해서 사용하므로 효율적 
- 사용자에 대한 응답성이 향상된다.
  카카오톡에서 동영상을 다운 받으면서, 채팅을 할 수 있는 이유 
- 작업이 분리되어 코드가 간결해진다. 

[주의]
- 같은 프로세스 내에서 자원을 공유하며 작업을 수행하기 때문에
  동기화, 교착상태, 경쟁상태 같은 문제들을 고려해서 프로그래밍해야 한다. 

쓰레드의 구현과 실행

JAVA에서 쓰레드를 구현하고 실행하는 방법 두 가지 
1. Thread 클래스를 상속 받아서 사용하는 경우
2. Runnbale 인터페이스를 구현하는 클래스 작성해서 사용하는 경우 

** 클래스 단일 상속 때문에, 일반적으로 Runnable 인터페이스를 구현해서 사용 ** 

쓰레드 구현

- 쓰레드를 구현한다는 것은 쓰레드를 통해 작업하고자 하는 내용으로 
  run()의 내용을 채우는 것이다. 

[중요 !!]
- Thread 클래스를 상속 받으면, 자손 클래스에서 
  조상인 Thread클래스의 메서드를 직접 호출 가능 
  → 쓰레드 정보가 담긴 필드를 모두 물려주기 때문

- Runnable를 구현하면 Thread 클래스의 static 메서드인 
  currentThread()를 호출하여 ** 쓰레드에 대한 참조를 얻어 와야만 현재 실행 중인 쓰레드 정보 접근 가능 **
  → 실행 중에 currentThread()를 호출하여 쓰레드 정보가 담긴 인스턴스를 가져와야 함 

class MyThread extends Thread {
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(getName());
        }
    }
}

class MyThread2 implements Runnable {
    public void run() {
        for(int i = 0; i < 5; i++){
            System.out.println(Thread.currentThread().getName());

        }
    }
}

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

        MyThread myThread = new MyThread();

        Runnable runnable = new MyThread2();

        Thread thread2 = new Thread(myThread);

        myThread.start();
        thread2.start();
    }
}

쓰레드 실행

- 쓰레드를 생성한 후, start() 메서드를 호출해서 실행해야 한다.
- start()가 호출된다고 바로 쓰레드가 실행되지 않음
  → start()가 호출되면 쓰레드 실행 대기열에 들어가게 됨 
- 한 번 실행된 쓰레드는 다시 실행할 수 없다.
  즉, 하나의 쓰레드에 대한 start()가 한 번만 호출될 수 있다.
- 쓰레드 작업을 한 번 더 수행해야 한다면  
  새로운 쓰레드를 생성한 다음 start()를 호출해야 한다. 

start()와 run()

- run()을 호출하는 것은 생성된 쓰레드를 실행하는 것이 아닌 단순히 클래스에 선언된 메서드를 호출하는 것 
  즉, 쓰레드 처리가 되지 않는다. 

- start()는 새로운 쓰레드가 작업을 실행하는데 필요한 ** 호출 스택을 생성 **한 다음 
  run()을 호출해서 생성된 호출 스택에 run()이 첫 번쨰로 올라가게 함 

** 즉, start()는 새로운 쓰레드를 생성하고, 쓰레드가 작업하는데 사용될 호출 스택을 생성 **
- 호출 스택 초기화 후 start()는 곧바로 종료된다. 

호출 스택 처리 과정

- 호출 스택은 쓰레드 개수 만큼 생성되고, CPU는 해당 호출 스택들을 번갈아 수행한다.
  즉, 호출 스택의 최상위에 있는 메서드라도 CPU가 해당 호출 스택을 실행하고 있지 않다면
  대기 상태에 있을 수 있다. 

- 작업을 마친 쓰레드는 호출 스택이 비어지면서, 사라진다.

main 쓰레드

- JAVA 프로그램이 실행될 때, 최초로 생성되고 실행되는 기본 스레드
  main() 메서드를 실행함 

- main 메서드가 수행을 마쳤더라도 
  다른 쓰레드가 아직 작업을 마치지 않았다면 프로그램을 종료되지 않는다.

** 실행 중인 쓰레드가 없을 때, 프로그램이 종료된다. **
- 메인 쓰레드가 예외로 인해 종료되어도, 다른 쓰레드가 종료되지 않았다면 프로그램은 종료되지 않는다.

싱글 쓰레드 VS 멀티 쓰레드

- 싱글 쓰레드
  싱글 코어에서 단순히 CPU만 사용하는 작업의 경우
  문맥교환으로 인해 싱글 쓰레드로 프로그래밍 하는 것이 더 효율적 

- 멀티 쓰레드 
  두 쓰레드가 서로 다른 자원을 사용하는 작업의 경우 (데이터 I/O, 네트워크 통신, 프린터 출력 등)
  멀티 쓰레드 프로세스가 더 효율적 

쓰레드 그룹

- 서로 관련된 쓰레드를 그룹으로 다루기 위한 것
- 쓰레드 그룹을 생성해서 쓰레드를 그룹으로 묶어서 관리
- 쓰레드 그룹 안에 다른 쓰레드 그룹을 포함시킬 수 있다.
- 자신이 속한 쓰레드 그룹이나 하위 쓰레드 그룹은 변경할 수 있지만
  다른 쓰레드 그룹의 쓰레드를 변경할 수 없다.
- ThreadGroup을 사용해서 생성할 수 있다.
- 모든 쓰레드는 반드시 쓰레드 그룹에 포함되어 있어야 한다.
  쓰레드 그룹 생성자를 사용하지 않은 쓰레드는 
  기본적으로 자신을 생성한 쓰레드와 같은 쓰레드 그룹에 속한다.
- JVM은 main과 system이라는 쓰레드 그룹을 만들고 
  JVM 운영에 필요한 쓰레드를 생성해서 이 쓰레드 그룹에 포함시킨다.
- 우리가 생성하는 모든 쓰레드 그룹은 main쓰레드 그룹의 하위 쓰레드 그룹이된다.
- 쓰레드 그룹을 지정하지 않으면 자동으로 main 쓰레드 그룹에 속하게 된다.
- 쓰레드 그룹에 쓰레드가 속해 있다면 가비지 컬렉터가 제거하지 않는다.
- 반면 쓰레드가 종료되어 쓰레드 그룹에서 제거되면 제거 대상이 된다.

// 쓰레드 자신이 속한 쓰레드 그룹을 반환 
ThreadGroup getThreadGroup(); 
// 쓰레드 그룹의 쓰레드가 처리되지 않은 예외에 의해 실행 종료되었을 때, 
// JVM에 의해 이 메서드가 자동으로 호출된다. 
void uncaughtException(Thread t, Throwable e);

데몬 쓰레드

- 일반 쓰레드의 작업을 돕는 보조적인 역할을 수행하는 쓰레드
- 일반 쓰레드가 모두 종료되면 데몬 쓰레드는 강제적으로 자동 종료된다.
  → 일반 쓰레드의 보조역할을 수행하기 떄문(필요 없어짐)

[데몬 쓰레드 예시]
- 가비지 컬렉터, 워드 프로세서의 자동저장, 화면 자동 갱신 등
- 데몬 쓰레드는 무한루프와 조건문을 이용해서 실행 후 대기하고 있다
  특정 조건이 만족되면 작업을 수행하고 다시 대기하도록 작성한다.
- 데몬 쓰레드와 일반 쓰레드 작성방법과 실행방법은 같음
- 쓰레드를 생성 후, 실행하기 전에 setDaemon(true)를 호출하기만 하면 된다.
  쓰레드 실행 후, setDaemon()을 실행하면 예외 발생


boolean isDaemin(); // 데몬 쓰레드인지 여부 판별
void setDaemon(boolean on); // 인자가 true인 경우 데몬쓰레드로 설정 


[getAllStackTraces()]
- 실행 중 또는 대기상태 즉, 작업이 완료되지 않은 모든 쓰레드의 호출 스택을 출력할 수 있다.
- 프로그램을 실행하면 JVM은 가비지컬렉션, 이벤트처리, 그래픽처리와 같은
  프로그램이 실행하는데 필요한 데몬 쓰레드를 자동적으로 생성해서 실행시킨다.
  이들은 system 또는 main 쓰레드 그룹에 속한다.

쓰레드 상태

NEW 
쓰레드가 생성되고 아직 start()가 호출되지 않은 상태

RUNNABLE
실행 중 또는 실행 가능한 상태

BLOCKED
동기화블럭에 의해 일시정지된 상태(lock이 풀릴 때까지 기다리는 상태)

WAITING, TIMED_WAITING
작업이 종료되지는 않았지만 실행 가능하지 않은 상태, 
일시정지 상태 TIMED_WAITING은 일시정지 시간이 지정된 경우를 의미

TERMINATED
쓰레드의 작업이 종료된 상태

쓰레드의 생명 주기

1. 쓰레드가 생성되고 start()가 호출되면 실행 대기 상태가 된다. 
2. 실행 대기 상태에 있다가 자신의 차례가 되면 실행 상태가 된다.
3. 주어진 실행 시간이 다 되거나 yield()를 만나면 다시 실행 대기 상태가 되고
   다음 차례 쓰레드 실행
4. 실행 중에 suspend(), wait(), join(), I/O block에 의해 일시정지 상태가 될 수 있다.
   I/O block은 입출력작업에서 발생하는 지연상태, Ex) 사용자 입력 대기 → 입력이 끝나면 실행대기 
5. 지정된 일시정지 시간이 다 되거나(time-out), notify(), resume(), interrupt()가 호출되면 
   일시정지 상태를 벗어나 다시 실행대기열에 저장되어 자신의 차례를 기다린다. 
6. 실행을 모두 마치거나 stop()이 호출되면 쓰레드는 소멸된다.

'JAVA' 카테고리의 다른 글

JAVA 상속, 캡슐화, 인터페이스  (0) 2025.02.08
지네릭스  (0) 2025.02.06
Object 클래스의 메서드  (0) 2024.12.04
객체지향 프로그래밍 1  (0) 2024.11.27
JAVA Collection Framework 3  (0) 2024.11.26