Java
2022. 11. 11. 01:26
Java의 정석 13장 리뷰
쓰레드를 구현할 때는
run()
메서드를, 실행할 때는start()
메서드를 호출한다. (p.726)- 쓰레드의 호출 스택을 별도로 생성하기 위해서이다.
쓰레드 그룹(thread group) (p.741)
- 보안 상 이유로 도입된, 쓰레드를 그룹으로 관리하는 클래스이다.
- 자신이 속한 쓰레드 그룹이나 하위 쓰레드 그룹은 변경할 수 있지만, 다른 쓰레드 그룹의 쓰레드를 변경할 수는 없다.
- 모든 쓰레드는 반드시 쓰레드 그룹에 포함되어 있어야 하며, 기본적으로는 부모 쓰레드와 같은 쓰레드 그룹에 속한다.
- JVM은 기본적으로 system과 main 쓰레드 그룹을 생성한다.
- 다음과 같이 참조 변수 없이 쓰레드를 생성하여 바로 실행시켜도, 이 쓰레드의 참조가 ThreadGroup에 저장되어 있기 때문에 GC 대상이 되지 않는다.
public static void main(String[] args) throws Exception { ThreadGroup grp1 = new ThreadGroup("Group1"); new Thread(grp1, () -> { try { Thread.sleep(1000); } catch (InterruptedException e) {} }, "th1").start(); }
- 보안 상 이유로 도입된, 쓰레드를 그룹으로 관리하는 클래스이다.
관련 메서드
getAllStackTraces()
- 작업이 완료되지 않은 모든 쓰레드의 호출스택을 출력할 수 있다.
Thread.sleep()
sleep()
메서드는 static으로 선언되어 항상 현재 실행 중인 쓰레드에 대해 작동하기 때문에, 다른 쓰레드 인스턴스 메서드를 호출하는 코드(th1.sleep(1000)
등)를 작성해도 해당 쓰레드가 아닌 현재 쓰레드가 WAITING 상태로 들어간다.
isInterrupted()
interrupted()
메서드와 달리,isInterrupted()
메서드는 쓰레드의 interrupted 상태를 false로 초기화하지 않는다.
suspend()
,resume()
,stop()
(p.755)suspend()
메서드는sleep()
메서드처럼 쓰레드를 멈추게 하고,resume()
메서드를 통해 실행대기 상태로 돌릴 수 있다.
stop()
메서드는 호출 즉시 쓰레드를 종료시킨다.
- 위의 메서드들은 데드락을 일으키기 쉽게 작성되어 있어서, deprecated 되었다.
yield()
(p.760)- 쓰레드의 남은 할당 시간을 포기하고 다시 실행대기 상태로 돌아가는 메서드이다.
- while 무한루프 +
Thread.sleep()
+Thread.yield()
조합으로 애플리케이션의 응답 속도를 높일 수 있다. (busy-waiting 방지)- (참고)
suspend()
나stop()
메서드 호출 시에interrupt()
메서드를 사용하면 Waiting 상태에서 바로 깨어나서 정지시키는 기법으로도 응답 속도를 높일 수 있다.
- (참고)
InterruptedException (p.754)
Thread.sleep()
메서드 실행 중(Waiting 상태) interrupt 신호를 받으면, 해당 메서드에서 InterruptedException이 발생하고 interrupted 상태는 false로 자동 초기화된다. 따라서 해당 코드 바깥의 try ~ catch 문까지 interrupt 신호가 전달되지 않는다.
Concurrency
synchronized 블럭
- 블럭이나 메서드에 synchronized 키워드를 붙이면, 내부 코드에 접근 시 자동으로 임계 영역이 설정된다.
synchronized(객체의 참조변수) { // Critical Section }
- 블럭이나 메서드에 synchronized 키워드를 붙이면, 내부 코드에 접근 시 자동으로 임계 영역이 설정된다.
wait()
,notify()
- synchronized 블럭은 특정 쓰레드가 객체의 락을 가진 상태로 오랜 시간 있을 수 있기 때문에, 이를 보완하고자 나온 메서드가 위의 두 가지이다.
- 두 메서드는 Object 클래스에 정의되어 있다.
notifyAll()
: 기다리는 모든 쓰레드에게 통보한다. 단, lock을 얻을 수 있는 건 하나의 쓰레드 뿐이다.
Lock
java.util.concurrent.locks
패키지(JDK 1.5 이상)의 lock 클래스들을 이용할 수도 있다.
ReentrantLock
- 가장 일반적인 Lock이다.
- 생성자에
ReentrantLock(boolean fair)
와 같이 fait 변수를 줄 수 있고, 이 값이 true면 가장 오래 기다린 쓰레드가 Lock을 획득하도록 한다. 하지만 이 과정에서 성능이 떨어지며, 대부분의 경우 공정함보다는 성능을 선택한다.
tryLock()
메서드는, 다른 쓰레드에 의해 Lock이 걸려있으면 기다리지 않거나 인자로 받은 시간만큼만 기다리고 Lock을 얻지 못하면 false를 반환한다. 당연히 얻으면 true를 반환한다.
ReentrantReadWriteLock
- 읽기 Lock이 걸려있을 경우, 다른 쓰레드가 읽기 Lock을 중복해서 걸고 데이터를 읽을 수 있다. 하지만 읽기 Lock과 쓰기 Lock을 동시에 걸 수는 없다.
StampedLock
- 읽기, 쓰기 Lock 외에 낙관적 읽기 Lock(optimistic reading lock)을 추가한 것이다. 읽기 Lock이 풀리는 것을, 쓰기 Lock이 기다리는 걸 방지하려고 낙관적 읽기 Lock을 만든 것이다.
- 낙관적 읽기 Lock은 쓰기 Lock에 의해 바로 풀린다.
- 즉, 쓰기와 읽기 충돌 시 쓰기가 끝난 후에 읽기 Lock을 건다.
Condition
- 쓰레드가 원하는 Lock의 종류를 구분하기 위한 waiting pool이다.
- Object 클래스의
wait()
,notify()
메서드 대신, Condition의await()
,signal()
메서드를 사용하면 된다.
volatile (p.786)
코어에서 변수의 값을 읽을 때, 캐시가 아닌 메모리에서 읽어오도록 강제하는 키워드로 사용한다.
- C, C++의 volatile 키워드와 문맥이 다른 것 같다. (C 계열에서는 최적화 방지 키워드로 사용)
fork & join (p.788)
- JDK 1.7부터 추가된, 하나의 작업을 작은 단위로 나눠서 여러 쓰레드가 동시에 처리하도록 하는 기법이다.
RecursiveAction
: 반환 값이 없는 작업 구현 시 사용
RecursiveTask
: 반환 값이 있는 작업 구현 시 사용
'Java' 카테고리의 다른 글
JIT 컴파일 (0) | 2023.01.14 |
---|---|
OpenJDK 아카이브 링크 (0) | 2022.12.10 |
백기선 스프링 프레임워크 핵심 기술 완강 후기 (0) | 2022.11.07 |
Java 이클립스 The project description file (.project) for ~ is missing 에러 (0) | 2021.12.13 |