Thread-1

Moom2n·2024년 3월 11일
0

Java

목록 보기
17/26
post-thumbnail

Process VS Thread

- Process

  • 실행 중인 프로그램을 의미한다.
  • 스케줄링의 대상이 되는 작업(task)과 같은 의미로 쓰인다.
  • Storage의 프로그램을 실행하면, 실행을 위해서 메모리 할당이 이루어지고 할당된 메모리 공간으로 바이너리 코드가 올라간다.
    -- 이 순간부터 process라고 불린다.
  • process는 하나 이상의 스레드 구성이 된다.

- Thread

  • process와 유사하지만, 메모리의 많은 부분을 공유한다.
  • Thread별로 Stack과 Register를 가지고 있음
  • Thread of Control을 줄인 말로, 제어의 흐름을 시각적으로 표현

% ps -ef | less

[쓰레드가 다른 쓰레드들과 공유하지 않는 메모리 영역]

  • Stack : 함수 호출 시의 매개변수, 리턴 값, 지역 변수 등이 저장된다.
  • PC Register : 각 쓰레드가 현재 실행 중인 명령어의 주소를 저장하는 레지스터.

[쓰레드가 다른 쓰레드들과 공유하는 메모리 영역]

  • Code : 프로그램의 실행 코드가 저장되는 영역이다.
  • Data : 전역 변수와 static 변수가 저장되는 메모리 공간이다.
  • Heap : 동적으로 할당된 데이터가 저장되는 공간이다.

- 차이점

- Single Thread

  • Main 스레드에서 작업 진행, 작업은 순차적으로 진행됨
  • 하나의 프로세스에서 오직 하나의 스레드로만 실행
    -- 단일 레지스터와 스택으로 구성
    -- Context Switching 작업을 요구하지 않음
    -- 동시성 제어에 대한 처리를 신경 쓰지 않아도 됨

- Multi Thread

  • 프로그램 내에서 두 개 이상의 동작을 동시에 실행
    -- 프로세서의 활동을 극대화
    -- 두 개가 동작을 동시에 실행
  • 하나의 프로세스를 다수의 실행 단위로 구분하여 자원을 공유
    -- 자원의 생성과 관리의 중복성 최소화
    -- 수행 능력 향상

Thread Class를 이용한 thread 구현

- Thread class

import java.time.LocalTime;

public class ThreadCounter extends Thread{
    private String name;
    private int count;
    private int max_count;

    public ThreadCounter(String name, int max_count) {
        this.name = name;
        this.count = 0;
        this.max_count = max_count;
    }
	
    @Override
    public void run() {
        while(count<max_count) {
            System.out.println(name + ", " + count);
            try {
                Thread.sleep(1000);
                count++;
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt(); // 현재 돌아가고 있는 쓰레드를 -> interrupt하라.
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ThreadCounter counter1 = new ThreadCounter("giuk1", 5);
        ThreadCounter counter2 = new ThreadCounter("giuk2", 5);

        System.out.println(LocalTime.now());
        counter1.start();
        counter2.start();
        System.out.println(LocalTime.now());
    }
}

InterruptedException은 스레드가 대기(waiting), 수면(sleeping), 또는 작업 중(blocked)일 때, 다른 스레드가 현재 스레드를 중단(interrupt)하려고 시도할 때 발생하는 예외

실행결과

15:03:15.519120
15:03:15.519409
giuk2, 0
giuk1, 0
giuk2, 1
giuk1, 1
giuk1, 2
giuk2, 2
giuk1, 3
giuk2, 3
giuk2, 4
giuk1, 4

main 메소드의 쓰레드와 ThreadCounter 클래스의 쓰레드가 서로 다르므로, 병렬 실행되는데, LocalTime.now() 메소드가 먼저 실행되는 것 처럼 보이게 된다.


Runnable interface를 이용한 구현

- Runnable Interface

Runnable interface는 run() 구현만을 요구하는 functional interface이다.

Runnable interface 구현시 실행을 위해서는 별도의 Thread object가 필요하다.

import java.time.LocalTime;

// Runnable 인터페이스는 run() 메서드 만을 구현하게 강제한다.
public class RunnableCounter implements Runnable {
    String name;
    int count;
    int maxCount;

    public RunnableCounter(String name, int maxCount) {
        this.name = name;
        this.maxCount = maxCount;
    }

    @Override
    public void run() {
        while (this.count < this.maxCount) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            this.count += 1;
            System.out.println(this.name + " : " + this.count);
        }
    }

    public static void main(String[] args) {
        RunnableCounter counter1 = new RunnableCounter("counter1", 10);
        RunnableCounter counter2 = new RunnableCounter("counter2", 10);
        
        Thread thread1 = new Thread(counter1);
        Thread thread2 = new Thread(counter2);

        System.out.println("start : " + LocalTime.now());

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

        while (thread1.isAlive() || thread2.isAlive()) { // 둘 다 작업이 끝날 때까지 기다려라.
            ;
        }

        System.out.println("end : " + LocalTime.now());
    }
}

- Thread Object 관리

  1. 생성 후 종료될때 자동 삭제되도록 한다.

Thread는 Runnable interfacerun() 수행이 끝나면 종료된다. 따라서, 일정한 작업을 외부의 간섭없이 수행하고 종료된다면 운영상에 크게 문제는 없다.
다만, 해당 thread를 원하는 시점에 종료시키거나 관련 정보 확인이 어렵다.

public class Exam01 {
    public static void main(String[] args) {
        RunnableCounter counter = new RunnableCounter("counter", 5);
        Thread thread = new Thread(counter);

        thread.start();
    }
}
  1. 구현되는 class내에 Thread object를 포함시켜 관리한다.

두번째 방법은 Runnable interface를 구현하는 class가 필요로 하는 Thread instanceclass 내에 포함시켜 관리할 수 있다.
이경우, 생성된 object에서 자신과 관련된 Thread instance를 관리하므로, thread 제어와 관련된 처리가 가능하다.

public class SelfRunnableCounter implements Runnable {
    int count;
    int maxCount;
    Thread thread;

    public SelfRunnableCounter(String name, int maxCount) {
        this.maxCount = maxCount;
        count = 0;
        thread = new Thread(this, name);
    }

    public void start() {
        thread.start();
    }

    @Override
    public void run() { }
    

Class 확장과 Interface 구현을 이용한 thread 구현 비교

Runnable로도 쓰레드의 구현이 가능하지만, 별개의 작업을 수행하지는 못한다.

[쓰레드를 상속하는 것과 인터페이스를 만드는 것의 차이점]

<클래스로 하는 것의 장점>
: 재활용이 가능하다

<인터페이스로 하는 것의 장점>
: 원래 기능에 추가해서 + run하다는 기능도 추가해 줄 수 있다.

=> Ball을 예시로 들어 보자. class에 extends Thread를 하게 되면 최상위 Object부터 해야 한다.
하지만, Runnable 인터페이스를 사용하면 내가 원하는 시점부터 implements를 선택적으로 할 수 있다.
인터페이스로 나중에 합치느냐 vs 클래스로 해서 근본부터 가느냐 => 이 차이는 구조 설계에서 하늘과 땅 차이다.

=> 움직이지도 않는 일반 Ball에 굳이 쓰레드를 구현해야 하느냐? 이는 리소스 낭비이자 독해력 낭비.


Thread 멈추기

Java의 thrad는 start()에 의해 시작되지만, 종료에 대한 명령이 없다. 정확히는 초기 버전에서는 stop()을 지원하였으나, 현재는 안전성을 이유로 사용하지 않을 것을 권장한다.

stop()은 thread를 즉시 중단시키는 method로서, thread가 실행 중인 상태에서 강제로 종료시켜 thread 내부에서 리소스 정리를 제대로 할 수 없게 되고, 이로 인해 프로그램이 예기치 않게 동작할 수도 있다.

또한, stop()으로 lock을 해제하지 않은 채 thread를 종료시켜 다른 thread에서 lock 획득을 위해 무한히 기다리는 deadlock 상태에 빠질 수 있다.

Thread를 안전하게 종료하기 위해서는 thread 내에서 확인 가능할 수 있도록 상태를 전달해 스스로 종료할 수 있게 만들어야 한다.

- 상태 제어 이용하기

Thread는 일정 작업을 수행하고 끝나거나 반복된 작업을 수행하도록 동작한다.

과정 수행이 자동 종료가 되던 반복된 작업을 하던 공통적인 문제는 중간에 중단 시키고 싶을 경우 내부 흐름 상에서 계속 진행할지에 대한 상태 표시가 필요하다.

- Interrupt 이용하기

Java Thread class에는 sleep이나 wait 상태일 때 외부로부터 이벤트를 전달 받을 수 있는 interrupt가 지원된다.

Interrupt는 Thread class에 상태 정보로도 사용되지만, sleep이나 wait와 같은 대기 상태에서 exception을 발생 시킨다.

이를 대기 상태의 thread를 runnable 상태로 변경할 수 있고, 설정된 interrupt를 통해서 추가적인 처리가 가능하다.

쓰레드를 중간에 중지시키고 싶으면,

Thread.currentThread().interrupt();

를 쓰면 된다.

1) 외부에서 thread 객체에 interrupt() 메서드를 호출해도 괜찮고,
2) 내부적으로 Runnable한 객체를 사용 중에 있다가,

Thread.currentThread().interrupt()

를 호출해도 된다.

구현되는 클래스 내에서 Thread를 멤버 field로 포함하면, 상관이 없다.

@Override
public void run() {
    running = true;

    while (running && (count < maxCount)) {
        try {
            ++count;
            System.out.println(getName() + " : " + count);
            Thread.sleep(1000);
        } catch (InterruptedException ignore) {
            Thread.currentThread().interrupt();
        }
    }
}

0개의 댓글

관련 채용 정보