티스토리 뷰

BackEnd/JAVA

Thread

JINSUKUKU 2021. 11. 18. 22:41

✏️ 프로세스(Process)

  • 프로세스 : 어플리케이션을 실행하면 OS로부터 메모리의 일정 영역을 할당받아 동작한다
  • 멀티 프로세스 : OS는 동시에 여러 프로세스를 실행할 수 있다
  • 프로세스는 동작하기위해 데이터, 메모리 등의 자원과 하나 이상의 스레드(Thread)로 구성된다
  • 멀티 프로세스가 동작하고, 동일한 어플리케이션을 실행하더라도 프로세스 간의 자원 및 스레드 공유는 발생하지 않는다

 

✏️ 스레드(Thread)

  • 프로세스 동작의 최소 단위를 말한다. 프로세스는 하나 이상의 스레드로 구성된다
  • 둘 이상의 스레드로 구성된 프로세스를 멀티 스레드 프로세스라고 말한다
    • 예를 들면 카카오톡은 채팅을 하는 동시에 파일을 전송할 수 있다
    • 단일 스레드 프로세스였다면, 파일을 전송하는 동안 채팅을 할 수 없다
    • 이처럼 스레드는 하나의 프로세스 내에서 동시 작업을 가능하게 한다

 

✏️ 멀티스레드 프로그래밍의 특징

1. CPU 활용률 향상

  • 멀티스레드의 사용으로 CPU의 활용률을 높일 수 있다

2. 응답성 향상

  • 스레드 단위로 작업을 분리하여 병렬 수행할 수 있다
  • 사용자의 입력을 기다리거나 파일전송 등의 시간이 걸리는 작업을 메인 스레드에서 분리하여 수행하면
  • 즉, 병렬 수행하면 어플리케이션의 응답성을 향상할 수 있다

3. 자원의 공유를 통한 효율성 증대

  • 하나의 프로세스안의 여러 스레드는 프로세스의 자원을 공유하므로 자원을 효율적으로 사용한다
  • 별도의 프로세스의 경우, 자원 공유를 위해 복잡한 과정을 거쳐야 한다

4. 컨텍스트 스위칭 비용 발생

  • 멀티스레드 프로그램을 위한 [참고1]컨텍스트 스위칭 과정으로 인해 비용이 발생한다
  • 싱글코어 CPU환경에서 A,B의 작업을 수행하는 경우, 순차적으로 수행 시 필요한 작업 시간은 아래 이미지와 같다
  • 반면 두 작업을 각각의 스레드로 수행하면 A,B작업을 교대하는 과정에서 컨텍스트 스위칭 비용이 발생한다
  • 이러한 작업 형태를 병행(Concurrent) 프로그래밍이라고 말한다 

5. 스레드 제어의 어려움

  • 하나의 스레드를 제어하는 싱글 스레드와 달리
  • 멀티 스레드 프로그래밍에서는 각 스레드의 상태를 파악하며 제어해주어야 한다는 어려움이 있다

 

✏️ 스레드 생성과 수행

Runnable 인터페이스를 구현

  • Runnable 인터페이스는 전형적인 함수형 인터페이스로, 람다식을 이용해 작성할 수 있다
  • Runnable 인터페이스의 run() 를 오버라이딩해서 필요한 기능을 구현한다
  • 스레드는 main() 가 아닌 run() 를 시작점으로 한다
Thread t1 = new Thread(new Runnable() {
	public void run(){
		System.out.println("Hello");
	}
});
  • 익명 내부 클래스를 사용해 Runnabel의 객체를 Thread의 생성자에 파라미터로 넘겨주어 Thread객체를 생성해준다
Thread t2 = new Thread(() -> { System.out.println("Hello"); });
  • 익명 내부 클래스를 사용한 코드와 동일한 기능을 하는 람다식이다

 

Thread클래스를 상속

  • Thread클래스는 Runnable을 구현하는 클래스이다
  • 그렇기 때문에 별도로 Runnable 객체를 파라미터로 넣을 필요 없이 Thread클래스만으로도 스레드를 만들 수 있다
class MyThread extends Thread{
	public void run(){
		System.out.println("Hello");
	}
}

Thread t3 = new MyThread();
  • Thread를 상속받으면 코드 작성이 편리해진다
  • 하지만 단일 상속의 제약에 의해 다른 클래스를 상속받을 수 없는 단점이 있다

 

✏️ run() VS start()

  • 스레드를 실행의 시작점인 run() 메소드를 실행하기 위해서는 Thread클래스의 start() 메소드를 호출해야한다
  • run() : 스레드에서 수행할 작업을 정의하는 메서드이다
  • start()
    • 스레드의 run() 메소드가 호출될 수 있도록 준비하는 과정이다
    • start() 가 호출되면, JVM의 운영체제 스레드 스케줄러에 의해 가능한 때에 run() 을 호출한다
    • 직접 main() 을 호출하지 않았듯, run() 을 직접 호출하지 않는다

 

✏️ 스레드의 상태와 제어

  • 스레드는 별도의 스택에서 따로 따로 동작하기때문에 제어하기에 어려움이 따른다
  • 스레드를 제어하기위해서는 상태를 정확히 이해하고 제어해야만한다
  • 스레드의 상태는 스레드 객체의 getState() 메서드를 통해 확인할 수 있다

 

✏️ 스레드의 생명 주기

1. 스레드 객체를 생성하면 동작은 하지 않는다 → start() 메서드 호출 → 스레드의 상태가 RUNNABLE로 변경된다

2. RUNNABLE 상태의 스레드가 스레드 스케줄러에 의해 선택되면 동작한다

    - 여러 스레드가 RUNNABLE 상태라면, 스레드간의 경합이 일어난다

    - 특정 스레드의 정확한 동작 시점은 알 수 없다

3. JVM에서 run() 을 호출하면 스레드가 동작한다

4. run() 메서드가 종료되면 스레드는 소멸한다. 한 번 소멸한 스레드는 더이상 동작하지 않는다

    - 다시 스레드가 필요하다면 다시 생성하고, start() 해야한다

5. 동작중인 스레드

    - 상태 제어 메서드의 호출이나 I/O에 의한 블로킹이 일어나면 대기상태로 변한다

    - 대기 상태에서의 스레드는 동작을 일시정지한다

6. 대기상태의 스레드

    - 상태 제어의 종료, I/O가 종료되는 경우 등의 상황에서 다시 RUNNABLE 상태로 돌아간다

    - RUNNABLE 상태로 돌아간 스레드는 다시 경합을 거쳐 동작해야한다

7. 스레드가 동작중인 상황에서 yeild() 메서드를 호출하면 동작이 멈춘다

    - 대기 상태로 이동하는 것은 아니고 RUNNABLE 상태로 돌아가 경합하게된다

8. 한 번의 생존 주기가 완전히 소멸된 스레드는 다시 start() 를 호출할 수 없다

    - 소멸된 스레드의 동작이 필요하다면 다시 새로운 스레드 객체를 생성하고 start() 를 호출해야한다

 

✏️ 스레드의 우선순위

  • 여러 스레드가 RUNNABLE 상태라면, 스레드간의 경합이 일어난다
  • 이 때 가급적 먼저 실행되어야하는 스레드는 우선순위가 높아야한다
  • 우선순위는 스레드의 중요도에 따라 1부터 10의 정수로 지정된다
  • 스레드 우선순위는 setPriority() getPriority() 메서드로 설정하고 가져온다
  • 무조건 스레드 우선순위에 의해 스레드의 실행 순서가 결정되는 것은 아니며, 실행 확률이 높아지는 것뿐이다
  • 우선순위만으로 스레드를 완벽하게 제어할 수 없다

 

✏️ 스레드의 상태 제어

  • sleep()
    • 동작하는 스레드를 주어진 시간동안 일시정지시켜 대기 풀에서 대기하도록 한다
    • 대기중인 스레드는 설정된 대기시간이 종료되거나, interrupt() 메서드가 호출되면 다시 RUNNABLE 상태가 된다
  • join()
    • 스레드를 대기 풀로 이동시킨다
    • 일반적으로 다른 스레드와의 선후관계가 있는 경우 사용된다
    • 선행되어야하는 스레드의 작업이 종료될 때까지 join() 을 호출한 스레드는 대기 풀에서 대기한다
  • interrupt()
    • sleep() join() 으로 인해 대기풀에서 대기중인 스레드를 임의로 RUNNABLE 상태로 만들 때 사용한다
  • yield()
    • 동일한 우선순위의 다른 스레드에게 실행을 양보한다
    • 양보하는 즉시 RUNNABLE상태로 변경한다 (대기상태X)

 

✏️ 스레드의 종료

  • run() 메서드가 끝나면 자동 종료된다
  • 외부의 요건에 의해 스레드를 종료해야할 때에는 stop() 메서드를 사용할 수 있다
  • 하지만 deprecated된 메서드이므로 사용하지 않아야한다
  • 안정적으로 스레드를 종료시키기 위해서는 플래그(flag)값을 사용해 내부에서 자연스럽게 종료할 수 있도록 처리해야한다
  • 한 번 종료한 스레드의 동작이 필요하다면 다시 새로운 스레드 객체를 생성하고 start() 를 호출해야한다

 

✏️ 데몬 스레드

  • 일반 스레드의 작업을 돕기위한 보조적인 스레드를 말한다
    • 문서 작업 : 일반 스레드
    • 자동 저장 : 데몬 스레드
  • 스스로 종료할 수도 있고, 다른 스레드들이 종료되는 상황에서도 종료한다
  • 데몬 스레드는 무한 루프를 이용, run() 메서드 이후 대기 상태에 있다가 조건에 따라 실행된다
    • 10분에 한번 자동 저장이 일어난다면, 이 때의 조건은 10분의 경과이다
  • 데몬 스레드는 setDaemon() 메서드를 이용해 생성한다

 

✏️ 멀티스레드의 문제점과 해결방법

  • 프로세스의 자원을 여러 스레드에서 공유한다는 장점이 있으나 이는 문제점이기도 하다
  • 예를 들어 동시에 같은 좌석을 예매하는 경우 문제가 발생한다. 이런 상황을 데이터의 신뢰성이 깨졌다고 말한다
  • 데이터의 신뢰성이 깨진 상황을 대비하기위해서는 synchronized 키워드를 사용한다
    • synchronized 키워드는 메서드 또는 블록에 사용한다
    • synchronized 블록은 스레드의 동시 작업을 허용하지 않는 공간이므로,
    • synchronized 블록이 많을수록 멀티스레드의 성능이 떨어지게된다
    • 따라서 필요한 곳에 적절히 사용하는 것이 중요하다

 

✏️ 스레드 풀(Thread Pool)의 활용

  • 작업이 동시에 진행된다고는 하나, CPU에도 한계가 있기에 여러 동시작업을 처리하는데에는 무리가 있다
  • 이런 경우 스레드 풀을 사용하면 의미없이 스레드가 많이 만들어지고 소멸되는 과정을 막을 수 있다

 

 


 

 

[참고1] Context Switching

  • 하나의 CPU는 여러 작업을 동시에 처리하는 것처럼 느껴지지만, 실제로는 그렇지 않다
  • CPU는 여러 스레드를 아주 빠르게 번갈아 수행하기때문에 동시에 처리하는 것처럼 느껴지는 것이다
  • 이러한 과정을 컨텍스트 스위칭(Context Switching)이라고 말한다

[참고2] 함수형 인터페이스

  • 하나의 추상 메소드만이 선언된 인터페이스만 람다식의 타겟 타입이 될 수 있다
  • 이러한 인터페이스를 함수적 인터페이스(functional interface)라고 한다.
  • 인터페이스 선언시 @FunctionallInterface Annotation 을 사용하면
  • 컴파일러가 두 개 이상 추상메소드가 선언되어있는 인터페이스인 경우, 컴파일 오류를 발생시킨다.

[참고3] 람다식

  • 배열을 정렬하기위해서는 Arrays클래스가 제공하는 sort() 메서드를 사용한다
  • 정렬의 기준을 문자의 순서가 아닌 다른 기준으로 바꾸고자 한다면 어떻게 해야할까?
  • 새로운 기능을 만들어 Arrays.sort()에 전달하면 되는데, 자바에서는 함수만을 전달할 수 없다
  • 기능을 가지는 클래스를 만들고, 클래스의 객체를 만들어 전달해야한다. 하나의 함수를 전달하기위해 객체가 필요하다
  • 함수형 프로그래밍에서는 자바와 다르게, 함수만을 전달할 수 있다
  • java8에서부터는 함수형 프로그래밍 형태의 람다식을 수용해 객체 대신 단순 코드 블록만을 전달할 수 있게 되었다

[참고4] Vector 와 ArrayList의 차이점

  • Vector
    • 모든 요소에 대해 Lock이 걸려져 있기 때문에 데이터의 신뢰성이 깨지는 이슈가 발생하지 않는다
    • 동기화 처리로 인해 데이터 입출력 정확도는 높다는 장점이 있다
    • 반면, 모두 동기화 처리되어있기때문에 프로그램이 무거워져 성능 저하로 인해 속도가 느리다는 단점이 있다
  • ArrayList
    • Vector와 거의 유사한 구조를 가졌으나, 동기화 처리가 되어있지 않다
    • 동기화 처리가 되어있지 않으므로 속도는 빠르나 데이터 안정성 확보에 대한 이슈가 있다
    • 데이터 안정성 확보이 필요한 경우에만 개발자가 직접 동기화 처리(synchronized) 를 걸어준다

[참고5] Thread 동기화 처리 / 데이터의 신뢰성이 깨지는 이슈

  • 영화관 예매 시스템에서 하나의 좌석에 대한 예매 진행 중에 동시 접근에 대한 이슈를 말한다
  • 여러 thread가 함께 사용되는 경우, 하나의 thread가 예매 서비스를 진행중이라면
  • 다른 thread에서는 같은 좌석에 대한 예매 서비스가 제한된다 (Locking)
  • 진행 중인 예매가 마무리되면 해당 좌석에 대한 서비스 제한이 풀린다
  • 이 때 Locking 을 위해서는 synchronized 키워드를 사용한다프로세스(Process)
    • 프로세스 : 어플리케이션을 실행하면 OS로부터 메모리의 일정 영역을 할당받아 동작한다
    • 멀티 프로세스 : OS는 동시에 여러 프로세스를 실행할 수 있다
    • 프로세스는 동작하기위해 데이터, 메모리 등의 자원과 하나 이상의 스레드(Thread)로 구성된다
    • 멀티 프로세스가 동작하고, 동일한 어플리케이션을 실행하더라도 프로세스간의 자원 및 스레드 공유는 발생하지 않는다

 

댓글
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
최근에 올라온 글
글 보관함
Total
Today
Yesterday