스레드, 요건 몰랐쥐

cutiepazzipozzi·2023년 6월 8일
1

지식스택

목록 보기
30/35
post-thumbnail

스레드는 옛날에 미리 공부해둔 경험이 있어 챕터의 글을 좀 더 재밌게 수월하게 읽을 수 있었던 것 같다. 아는 만큼 보인다는 말이 이런 말일까,, 아무튼 개념 정리는 지난번에 했었기 때문에 내가 제대로 몰랐던 사실 위주로 스레드를 기록하고자 한다!

프로세스와 스레드

둘 모두 운영 체제에서 실행되는 프로그램의 실행 단위인데, 무슨 차이?!
프로세스 = 공장, 스레드 = 일꾼
왜냐구? 스레드는 프로세스의 자원을 활용해 실제로 작업을 수행하기 때문!

  1. 프로세스는 최소한 하나의 스레드를 가지며, 이 스레드는 프로세스의 실행을 시작하고 종료한다.
  2. 프로세스 내의 여러 스레드는 프로세스의 메모리 공간을 공유하고, 이를 통해 데이터를 주고받으며 작업을 수행할 수 있다.
  3. 한 프로세스 내의 스레드는 동기화를 위해 lock과 같은 동기화 메커니즘을 사용할 수 있다.
  4. 프로세스는 운영 체제로부터 자원을 할당받는데, 이 자원은 프로세스 내의 모든 스레드에게 공유된다.
  5. 한 프로세스가 종료되면 그에 속한 모든 스레드도 함께 종료된다.

둘의 차이

GPT에게 심심한 감사를 건네며..

  1. 정의:
    프로세스: 운영 체제로부터 자원을 할당받아 실행되는 독립적인 프로그램 단위
    스레드: 프로세스 내에서 실행되는 작은 실행 단위로, 하나의 프로세스 내에서 동시에 실행될 수 있는 코드의 흐름을 나타냅니다.

  2. 자원 공유:
    프로세스: 각각의 프로세스는 독립된 메모리 공간, 파일, 입출력 장치 등을 가지며, 다른 프로세스와는 메모리 공유가 일어나지 않습니다. 프로세스 간에 데이터를 주고받기 위해선 특별한 통신 메커니즘을 사용해야 합니다.
    스레드: 같은 프로세스 내에서 스레드들은 메모리 공간을 공유합니다. 스레드는 프로세스의 주소 공간, 데이터, 스택 등을 공유하여 서로간에 데이터를 주고받고 동시에 작업을 수행할 수 있습니다.

  3. 생성과 제거:
    프로세스: 프로세스는 운영 체제에 의해 독립적으로 생성되고 제거됩니다. 프로세스는 운영 체제로부터 자원을 할당받아 실행되며, 각각의 프로세스는 최소한 하나의 스레드(메인 스레드)를 가지고 있어야 합니다.
    스레드: 스레드는 프로세스 내에서 생성되고 제거됩니다. 한 프로세스 내에서 여러 개의 스레드가 동시에 실행될 수 있으며, 스레드는 하나의 프로세스에 속해있어야 합니다.

  4. 독립성:
    프로세스: 각각의 프로세스는 독립된 실행 단위로, 다른 프로세스의 영향을 받지 않고 독립적으로 실행됩니다. 각 프로세스는 독립된 주소 공간을 가지고 있어, 하나의 프로세스에 문제가 발생하더라도 다른 프로세스는 영향을 받지 않습니다.
    스레드: 스레드는 하나의 프로세스 내에서 실행되는 작은 실행 단위로, 같은 프로세스 내의 스레드는 서로 동시에 실행

  5. 오버헤드
    프로세스: 프로세스 간의 전환(context switching)은 상대적으로 오버헤드가 큽니다. 이는 각 프로세스가 독립된 메모리 공간을 가지기 때문에, 상태 정보를 저장하고 복원해야 하기 때문입니다.
    스레드: 스레드 간의 전환은 프로세스 간의 전환보다 오버헤드가 적습니다. 스레드는 같은 프로세스의 자원을 공유하므로, 상태 정보를 저장하고 복원하는데 필요한 작업이 적어집니다.

스레드

스레드의 개수가 결정되어있진 않으나, 프로세스의 메모리공간(호출스택)에 따라 개수가 제한된다.

  • 스레드는 별다른 클래스 이름을 지정하지 않으면 "Thread-번호" 요렇게 지정된다.
  • 스레드는 한번 사용되면 재사용이 불가하다.

스레드 실행 제어


위의 그림처럼 스레드는 5가지 상태로 나뉜다.

스레드의 동기화

동기화는 하나의 자원을 여러 개의 스레드가 공유하면서 서로의 결과에 영향을 주기 때문에 이를 방지하는 기법이다. (멀티스레드에서 발생할 수 있는 문제)

  1. Synchronized 키워드
    (1) 특정 객체에 lock 걸기
synchronized(객체 참조변수) {
	...
}

여기서 지정된 객체는 이 블록의 실행이 끝나면 lock이 풀린다. 이때 두 스레드가 lock을 건 상태에서 서로의 lock이 풀리길 기다리는 교착상태에 빠지지 않도록 조심해야 한다!!
(2) 메서드에 lock 걸기

public synchronized void cal {
	...
}

여기에 정석 p. 669의 예시 기록

p.664 스레드 간의 객체 공유 방법 작성하기!!

(ex. 계좌의 잔고가 0보다 작아지면 더 이상의 출금이 이뤄지지 않도록 막는 코드를 작성했으나, 스레드 간에 객체 공유 시 0보다 작아져 출금이 막아지기 전에 다른 스레드에서 출금을 하여 문제가 발생함)

  1. Lock 인터페이스 (근데 자바의 정석에서는 다루지 않는다. JAVA 8 버전이라 그런가..?)
    아래 세개의 클래스로 구현한다.
    (1) ReentrantLock (일반적!)
    (2) ReentrantReadWriteLock (읽기, 쓰기 Lock이 따로 있는)
    (3) StampedLock (위에 낙관적인 Lock 기능 추가.. 그러나 알 필요 X)

  2. wait()과 notify() (Object 클래스의 메서드로 어디에서든 사용 가능하다)
    도대체 뭐가 좋냐 물으신다면, 우리가 식당 웨이팅 중이라고 가정해보자.
    당신은 대기실에 앉아 기다리다가 자리가 나면 호출 받는 것이 좋은가, 아니면 무대뽀로 앞에 다같이 서서 기다리는 것이 좋은가? wait과 notify는 전자의 경우를 제공해준다.

  • 동기화 블럭 내에서만 사용할 수 O
  • 스레드 -> wait() -> Lock 풀림 -> wait()이 호출된 대기실에서 기다림(실행 대기 상태)
  • notify() -> 사용할 스레드만 깨움 (누굴 깨울지 알 수 X) -> 우선순위 높은 스레드가 너무 오래 기다릴 수 있음
  • notifyAll() -> 모든 스레드 깨움 -> JVM의 스레드 스케줄링에 의해 처리되도록
  • wait(): lock의 제어 풀어줌 / sleep(): 여전히 lock을 쥐고 있음

멀티 스레딩

여러개의 스레드가 동시에 실행된다고 하여 멀티 스레딩이라 불린다.
그러나 사실은, 한개의 CPU가 한가지 동작만 수행할 수 있기 때문에 아주 짧은 시간 안에 스레드들이 번갈아가며 실행되고 있어 멀티 스레드처럼 보이는 것이다.
(ex. 메신저 -> 채팅 하면서 파일도 다운로드 받는)

이때 스레드의 작업전환(Context Switching)에 지연되는 시간이 발생해 싱글 스레드의 실행시간과 비슷하거나 더 들어갈 수도 있다.
(** 작업 전환시에는 현재 진행중인 작업의 다음 실행위치,, 등의 정보를 전달한다)

그러면 도대체 장점이 무엇인고, 하니 지난번 스레드 포스팅에서 작성한 장점도 있지만, 여기서 강조하는 장점은 CPU 이외의 자원을 사용(ex. 사용자에게 입력을 받는)하는 경우에는 시간 낭비를 줄일 수 있다!!

** 그래서 main메서드가 종료되어도 다른 스레드는 아직 실행중일 수 있다..!

run() -> start()

우리가 Runnable 인터페이스나, Thread 클래스의 run 메서드를 실행할 때, run이 아닌 start 메서드를 호출하고 있음을 볼 수 있었다. 이는 run이라는 메서드는 단순히 클래스에 속한 메서드를 호출할 뿐이기 때문이다(싱글스레드)! 그렇기 때문에 start메서드로 동작을 대신한다.
(start메서드는 JVM을 활용하여 스레드 그룹에 해당 스레드를 추가해 각자의 스레드에서 실행될 수 있도록 한다)

  1. main 메서드가 start() 호출
  2. 새로운 호출스택 생성
  3. 생성된 호출 스택이 run() 호출
  4. 호출스택이 2개이므로 스케쥴러 순서에 의해 번갈아가며 실행

근데 사실 start()를 호출했다고 해서 이 스레드가 바로 실행되는 것은 아니다.
JVM의 스레드 스케쥴러가 정한 순서에 실행되기 때문에, 기다려야 한다..
(여기서 스레드 스케쥴러에 규칙이 있나?? 하고 찾아봤지만 별다른 수확은 없었고
스케쥴러가 불확실 하기 때문에 너무 의존하지 말라는 결론만 얻었다..
=> 스케쥴러의 이 특성때문에 자바가 OS 독립적이라고 하지만 스레드는 종속적이라고 불리우는 예시가 되었다)

스레드의 우선순위

  • 시각적인 부분 / 사용자에게 빠르게 반응해야 하는 작업의 우선순위 ↑
  • setPriority(숫자), getPriority()를 통해 우선순위를 저장하고 가져올 수 있음
  • 본인을 생성한 스레드로부터 우선순위를 상속 받는다!

스레드 그룹

= 서로 관련된 스레드를 그룹으로 다루기 위한 것.

  • 본래 목적은 보안 => 애플릿을 격리시키기 위해 탄생
//사용법이 궁금해 찾아와봤다.
ThreadGroup tg1 = new ThreadGroup("Group A");   
Thread t1 = new Thread(tg1,new MyRunnable(),"one");     
Thread t2 = new Thread(tg1,new MyRunnable(),"two");     
Thread t3 = new Thread(tg1,new MyRunnable(),"three");    
  • 그러나 ThreadGroup.uncaughtException()메서드만 유용하며 나머지는 1.5 버전 이후 ThreadGroup안의 메서드와 비슷한 기능의 메서드들이 탄생해 무용지물하게 되었다..

++ 지양하는 이유가 더 자세히 궁금하여 ChatGPT를 활용해 보았다..

  1. 유연성 부족: 스레드 그룹은 스레드를 계층 구조로 구성하여 그룹 내에서 스레드를 조작하고 관리하는 기능을 제공합니다. 그러나 이러한 계층 구조는 일반적으로 필요하지 않으며, 복잡성을 증가시킬 수 있습니다. 또한, 스레드 그룹은 동적인 스레드 관리에 제한을 가할 수 있어 유연성이 부족할 수 있습니다.

  2. 관리의 어려움: 스레드 그룹은 스레드의 생명주기 및 우선순위, 예외 처리 등을 관리하기 위한 메서드를 제공합니다. 하지만 스레드 그룹을 사용하면 스레드의 관리가 더 복잡해지고, 스레드 간의 의존성과 상호작용이 불분명해질 수 있습니다. 따라서 스레드 그룹 대신에 Executor 또는 스레드 풀과 같은 고수준의 스레드 관리 메커니즘을 사용하는 것이 더 효과적입니다.

  3. 이식성 문제: 스레드 그룹은 플랫폼마다 동작 방식이 다를 수 있습니다. 따라서 스레드 그룹을 사용하면 애플리케이션이 특정 플랫폼에 종속되고 이식성 문제가 발생할 수 있습니다. 이에 반해, 스레드 그룹을 사용하지 않는다면 애플리케이션 코드는 플랫폼 중립적인 구조를 유지할 수 있습니다.

  4. 대안의 존재: 스레드 그룹을 대체할 수 있는 다른 기능과 라이브러리가 존재합니다. 예를 들어, Java 5부터 도입된 Executor 프레임워크와 관련된 클래스들은 스레드의 실행과 관리를 더욱 쉽게 처리할 수 있게 해줍니다. 또한, 자바 컨커런트(Java Concurrency) 유틸리티 패키지는 스레드 관리와 동시성 프로그래밍을 위한 다양한 기능과 클래스를 제공합니다. 이를 사용하면 스레드 그룹을 사용하지 않고도 스레드의 실행과 관리를 더욱 쉽게 처리할 수 있습니다.

4-1. Executor 및 ExecutorService 인터페이스: Executor 프레임워크는 작업을 스레드에 할당하고 실행하는 메커니즘을 제공합니다. ExecutorService는 Executor의 하위 인터페이스로, 작업의 실행, 스레드 풀의 관리, 작업의 스케줄링 등을 보다 편리하게 처리할 수 있습니다.

4-2. 스레드 풀(Thread Pool): 스레드 풀은 사전에 생성된 스레드 집합으로 작업을 처리하는 데 사용됩니다. ExecutorService를 사용하여 스레드 풀을 생성하고 작업을 제출하면, 스레드 풀이 작업을 자동으로 할당하고 관리합니다.

4-3. 동기화(Concurrency) 컬렉션: java.util.concurrent 패키지에는 여러 동기화 컬렉션 클래스가 포함되어 있습니다. 이러한 컬렉션은 여러 스레드가 동시에 접근할 수 있는 환경에서 안전하게 데이터를 저장하고 조작할 수 있도록 지원합니다. 예를 들어, ConcurrentHashMap, ConcurrentLinkedQueue, BlockingQueue 등이 있습니다.

4-4. 동시성 유틸리티 클래스: java.util.concurrent 패키지에는 스레드 간의 협력적인 동작을 돕는 다양한 클래스가 있습니다. 예를 들어, CountDownLatch, CyclicBarrier, Semaphore 등이 있으며, 이러한 클래스를 사용하여 스레드의 동기화와 상호작용을 구현할 수 있습니다.

이러한 자바 컨커런트 유틸리티들은 스레드 관리와 동시성 프로그래밍을 더욱 효과적이고 안전하게 처리할 수 있는 방법을 제공합니다. 따라서 스레드 그룹보다는 이러한 기능과 클래스를 사용하는 것이 권장됩니다.

데몬 스레드

= 일반 스레드의 작업을 돕는 보조적인 역할 수행

  • 일반 스레드가 종료되면 강제적으로 자동 종료
    (ex. main문, GC, 자동저장)
    GC: 더 이상 참조되지 않는 객체를 정리하며, 프로그램 실행 중 계속해서 백그라운드에서 실행되며 메모리 관리를 지원함
    자동 저장: 프로그램의 특정 상태를 주기적으로 저장하거나 DB or 외부 소스에서 자동으로 업데이트 하는 작업은 데몬 스레드에서 수행됨
    네트워크 리스닝: 데몬 스레드가 계속해서 연결 요청을 수신하고 처리하며, 메인 스레드는 클라이언트 요청을 처리하는데 집중할 수 있음)
  • 무한루프, 조건문 사용
  • setDaemon(boolean ~) 메서드는 반드시 start() 호출 전 실행돼야 함

참고

자바의 정석, 2nd Edition.
https://www.javatpoint.com/threadgroup-in-java

profile
노션에서 자라는 중 (●'◡'●)

0개의 댓글