- [Java 정리] Thread 42022년 08월 22일
- starryeye
- 작성자
- 2022.08.22.:21
이미 알던 지식이지만, 기본으로 돌아가서.. Java의 관점에서 한번 정리해보자.
쓰레드의 동기화
멀티쓰레드 프로세스의 경우 여러 쓰레드가 같은 프로세스 내의 자원을 공유해서 작업하기 때문에
서로의 작업에 영향을 주게된다.
따라서, 한 쓰레드가 특정 작업을 끝마치기 전까지
다른 쓰레드에 의해 방해받지 않도록 하는 것이 필요하다.
그래서 도입된 개념이 임계 영역(Critical section)과 락(lock)이다.
공유 데이터를 사용하는 코드 영역을 임계 영역으로 지정해놓고,
공유 데이터(객체)가 가지고 있는 lock을 획득한 단 하나의 쓰레드만 이 영역 내의 코드를 수행할 수 있도록 한다.
그리고 해당 쓰레드가 임계 영역 내의 모든 코드를 수행하고 벗어나서
lock을 반남해야만 다른 쓰레드가 반납된 lock을 획득하여 임계 영역의 코드를 수행할 수 있게 된다.
이처럼, 한 쓰레드가 진행 중인 작업을 다른 쓰레드가 간섭하지 못하도록 막는 것을
쓰레드의 동기화(Synchronization) 라고 한다.
동기화를 안하고 개발할 경우엔..
1. ConcurrentModificationException 예외 발생
-> 어떤 쓰레드에서 write중에 다른 쓰레드가 read를 해버릴 경우 발생한다.
-> (참고, 싱글 쓰레드에서는 for loop 내부에서 Collection 객체를 수정할 경우도 발생할 수 있다)
2. IndexOutOfBoundsException 예외 발생
-> 이미 없어진 데이터에 접근할 경우 발생한다.
synchronized 키워드를 이용한 동기화
임계 영역을 설정하는데 사용된다.
public synchronized void test() { //메서드 단위의 임계 영역 ... } -------------------------------------------------------- ... synchronized(객체의 참조변수) { //코드의 일부를 임계 영역으로 설정 //참조변수는 락을 걸고자하는 객체를 참조하는 것이다. } ...
두가지 방법이 존재한다.
첫 번째 방법은 메서드 앞에 synchronized 키워드를 붙인다.
해당 메서드 전체가 임계 영역으로 설정된다.
두 번째 방법은 메서드 내의 코드 일부를 블럭{}으로 감싸고 블럭 앞에 synchronized(참조변수)를 붙인다.
락을 걸고자 하는 객체의 참조를 참조변수로 넣어준다.
두가지 방법 모두 lock의 획득과 반납이 자동으로 코드 진입할 때와 빠져 나올때 이루어진다.
<참고>
모든 객체 인스턴스는 lock을 하나씩 가지고 있다.
해당 객체 인스턴스의 lock을 가지고 있는 쓰레드만 임계 영역의 코드를 수행할 수 있다.
다른 쓰레드들은 lock을 얻을 때까지 기다린다.
Object 클래스의 wait(), notify()
wait()
동기화된 임계영역의 코드를 수행하다가 작업을 더 이상 진행할 상황이 아니면,
wait() 메서드를 호출하여 쓰레드가 락을 반납하고 기다리게 할 수 있다.
notify()
락을 얻은 쓰레드가 작업을 하다가 락을 반납한 쓰레드가 작업을 진행 할 수 있는 상황이 되면,
락을 얻은 쓰레드가 notify() 메서드를 호출하여 락을 반납한 쓰레드가 다시 락을 얻어 작업을 진행 할 수 있도록 한다.
해당 과정을 더 자세하게 알아보자..
실행 중이던 쓰레드가 wait() 메서드를 호출하면, 락을 반납하고 해당 객체의 waiting pool에서 통지를 기다린다.
notify() 메서드가 호출되면, 해당 객체의 waiting pool에 있던 모든 쓰레드 중에서 임의의 쓰레드만 통지를 받는다.
notifyAll()은 기다리고 있는 모든 쓰레드에게 통보를 한다. (Race Condition)
waiting pool은 객체 인스턴스마다 존재하는 것이다.
public final native void notify(); public final native void notifyAll(); public final void wait() throws InterruptedException { wait(0L); } public final native void wait(long timeoutMillis) throws InterruptedException; public final void wait(long timeoutMillis, int nanos) throws InterruptedException { if (timeoutMillis < 0) { throw new IllegalArgumentException("timeoutMillis value is negative"); } if (nanos < 0 || nanos > 999999) { throw new IllegalArgumentException("nanosecond timeout value out of range"); } if (nanos > 0) { timeoutMillis++; } wait(timeoutMillis); }
Object 클래스의 wait(), notify(), notifyAll() 메서드 정의부 이다.
<참고>
wait(), notify(), notifyAll() 메서드는 동기화 블럭 내에서만 사용 가능하다.
-> 락을 획득한 쓰레드만 해당 메서드를 호출할 수 있는 것이다.
<문제점>
wait() 메서드와 notify() 메서드를 이용한 동기화 방법의 문제는
선별적인 통지가 불가능 하다는 점에 있다.
쓰레드를 구분하여 통지하는 것이 불가능 하므로..
이론 상 쓰레드의 기아 현상과 경쟁 상태를 피하지 못한다.
(영원히 lock을 획득 하지 못하는 현상, lock을 획득 함에 있어서 다른 쓰레드와 경쟁하는 상황)
'Java' 카테고리의 다른 글
[Java 정리] Thread 6 (0) 2022.08.22 [Java 정리] Thread 5 (0) 2022.08.22 [Java 정리] Thread 3 (0) 2022.08.19 [Java 정리] Thread 2 (0) 2022.08.13 [Java 정리] Thread 1 (0) 2022.08.13 다음글이전글이전 글이 없습니다.댓글