자바의 synchronized는 무한대기와 공정성 문제가 존재하기 때문에, 한계가 존재합니다
이런 문제를 해결하기 위해, 자바 1.5부터 java.util.concurrent
라는 동시성 문제 해결
라이브러리를 제공하고 있습니다
이번에는 가장 기본이 되는 LockSupport부터 알아보며, synchronized의 단점인
무한 대기를 해결하는 방법을 알아보겠습니다
LockSupport는 스레드를 WAITING 상태로 변경합니다
WAITING은 CPU 실행 스케줄러에 들어가지 않고 누가 깨우기 전까지 계속 대기합니다
public class Main{
public static void main(String[] args){
Thread thread = new Thread(new MyTask(), "test");
thread.start();
sleep(1000);
Locksupport.unpark(thread);
}
static class MyTask implements Runnable {
@Override
public void run(){
LockSupport.park();
}
}
}
main 스레드가 test 스레드를 호출하고, test 스레드는 park를 호출합니다
test 스레드는 RUNNABLE에서 WAITING 상태가 되며 대기합니다
다시 메인 스레드에서 unpark를 호출하면서 WAITING 상태에서 RUNNABLE 상태로 변합니다
이렇게 LockSupport는 특정 스레드를 WAITING 상태나 RUNNABLE 상태로 변경할 수 있습니다
참고로 LockSupport의 park()는 매개변수가 없고, unpark(thread)는 매개변수가 있는데,
LockSupport.park()를 호출해서 실행중인 스레드가 스스로 대기상태에 빠질 수 있지만
대기상태의 스레드는 자신의 코드를 실행할 수 없고, 외부 스레드의 도움을 받아야하기 때문입니다
WAITING 상태에서 스레드를 인터럽트하면 RUNNABLE 상태로 변하고 깨어납ㄴ디ㅏ
parkNanos(nanos)를 통해 스레드를 나노초 동안만 TIMED_WAITING 상태로 변경합니다
지정한 나노초가 지나면 RUNNABLE 상태가 됩니다
BLOCKED 상태는 인터럽트가 되어도 대기 상태를 빠져나오지 못하고 여전히 BLOCKED 상태입니다
또한 WAITING, TIMED_WAITING 상태는 인터럽트가 되면 대기상태를 빠져나옵니다
그래서 RUNABBLE 상태로 변합니다
BLOCKED 상태는 자바의 synchronized에서 락을 획득하기 위해 대기할 떄 사용합니다
WAITING, TIMED_WAITING 상태는 스레드가 특정 조건이나 시간동안 대기할 때 발생하는 상태이며,
WAITING 상태는 Thread.join(), LockSupport.park(), Object.wait()와 같은 메소드를 호출할때
사용합니다
TIMED_WAITING 상태는 시간제한이 있는 대기에서 메소드를 호출할 떄 발생합니다
LockSupport를 사용하면 스레드를 WAITING, TIMED_WAITING 상태로 변경할 수 있고,
인터럽트를 받아 스레드를 깨울 수 있습니다
이를 잘 활용하면 synchronized의 무한대기 문제를 해결할 수 있습니다
parknanos()를 사용해서 특정 시간까지만 대기하도록 하거나, park(), parkNanos를 통해
인터럽트를 걸 수 있습니다
하지만 이렇게 설계하기 위해서는 LockSupport를 활용해서 안전한 임계영역을 만들기 위한
구현이 필요합니다
락이라는 클래스를 만들고 특정 스레드가 먼저 락을 얻으면 RUNNABLE, 락을 얻지 못하면
park()로 대기 상태를 만듭니다
락을 반납하면 unpark를 사용해서 대기중인 다른 스레드를 깨우거나 너무 오래 대기하면
parkNanos()로 스레드가 스스로 중간에 깨어나도록 할 수 있습니다
하지만 이를 위해 자료구조부터 우선순위까지 너무 고려할 사항이 많습니다
따라서 이러한 문제를 해결할 수 있는 새로운 방법을 찾아야합니다
그리고 그것이 바로 ReetrantLock입니다