자바 1.5부터 Lock 인터페이스와 ReentranceLock 구현체를 제공하고 있습니다
ReentranceLock을 이용해서 Synchronized의 단점인 공정성 문제를 해결할 수 있습니다
Lock 인터페이스는 동시성 프로그래밍에서 안전한 임계영역을 위한 락을 구현하는데 사용합니다
대표적인 구현체로 ReentrantLock이 있습니다
락을 획득하는 메소드입니다
만약 다른 스레드가 이미 락을 획득했으면, 락이 풀릴 때까지 현재 스레드는 WAITING됩니다
이 메소드는 인터럽트에 응답하지 않습니다
락 획득을 시도하지만, 다른 스레드가 인터럽트할 수 있도록 합니다
다른 스레드가 이미 락을 획득했으면 현재 스레드는 락을 획득할 때까지 대기합니다
대기중일 때 인터럽트가 발생하면 InterruptedException
이 발생하고 락 획득을 포기합니다
락 획득을 시도하고 성공 여부를 반환합니다
다른 스레드가 락을 획득했으면 false를 리턴하고, 그렇지 않으면 락을 획득한 뒤 true를 리턴합니다
주어진 시간동안 락 획득을 시도합니다
시간안에 락을 획득하면 true를 반환하며, 실패할 경우 fals를 반환합니다
또한 이 메소드는 대기 중에 인터럽트가 발생하면 InterruptedException
이 발생하며, 락 획득을 포기합니다
락을 해제하고, 락 획득을 대기중인 스레드 중 하나가 락을 획득할 수 이씃ㅂ니다
락을 획득한 스레드가 호출해야하며, 그렇지 않으면 IllegalMonitorStateException
이 발생할 수 있습니다
Condition 객체를 생성해서 반환합니다
Condition 객체는 락과 결합해서 사용되고 스레드가 특정 조건을 기다리거나 신호를 받도록 합니다
Object 클래스의 wait, notify, notifyAll 메소드와 유사한 역할을 합니다
lock() 메소드는 인터럽트가 발생해도 무시하고 락을 기다립니다
WAITING 상태의 스레드에서 인터럽트가 발생하면 대기상태를 빠져나오는데,
lock()을 호출해서 락을 얻으려고 대기중일 때 인터럽트가 순간적으로 발생해서 WAITING에서
RUNNABLE 상태가 되긴 하지만 다시 WAITING상태로 강제변경합니다
이렇게 인터럽트를 무시하기 때문에 인터럽트되지 않는다는 것이고
대신 인터럽트가 필요하면 lockInterruptibly()를 사용하면됩니다
ReentrantLock은 공정성 모드와 비공정 모드로 설정할 수 있습니다
비공정 모드는 ReentrantLock의 기본 모드로 락을 먼저 요청한 스레드가 락을 먼저 획득하는 보장이 없습니다
락을 풀었을 때, 대기중인 스레드 중 아무나 락을 획득할 수 있습니다
생성자에 true를 전달하면 됩니다
공정 모드는 락을 요청하는 순서대로 스레드가 락을 획득할 수 있도록 합니다
이로인해 먼저 대기한 스레드가 먼저 락을 획득하게 되어 스레드간 공정성을 보장합니다
다만 이로인해 성능이 저하될 수 있습니다
비공정 모드는 성능 중시 및 스레드가 락을 빨리 획득할 수 있지만
특정 스레드가 락을 계속 획득하지 못하는 기아현상이 발생할 수 있습니다
공정 모드는 스레드가 락을 획득하는 순서를 보장해서 공정성을 중시하지만 성능이 저하될 수 있습니다
public class Main {
private int sharedResource;
private final Lock lock = new ReentrantLock();
public Main(int init){
this.sharedResource = init;
}
public void logic(int res){
lock.lock();
try{
// 작업 시작
}finally{
lock.unlock();
}
}
}
임계영역에 대해 synchronized(this) 대신에 lock.lock()을 사용해서 락을 걸어줍니다
unlock() 전까지 임계영역이 되며, finally를 통해 반드시 unlock()을 실행하도록 합ㄴ디ㅏ
public class Main {
private int sharedResource;
private final Lock lock = new ReentrantLock();
public Main(int init){
this.sharedResource = init;
}
public void logic(int res){
if(!lock.tryLock()){
// 진입 실패
}
try{
// 작업 시작
}finally{
lock.unlock();
}
}
}
tryLock() 메소드를 통해 위와같이 lock을 얻을 수 없으면 바로 진입 실패 로직으로 이어지고
얻을 수 있으면 얻은 뒤, 임계영역이 시작되도록 할 수 있습니다
public class Main {
private int sharedResource;
private final Lock lock = new ReentrantLock();
public Main(int init){
this.sharedResource = init;
}
public void logic(int res){
if(!lock.tryLock(1000, TimeUnit.MILLISECONDS)){
// 진입 실패
}
try{
// 작업 시작
}finally{
lock.unlock();
}
}
}
이렇게 1초동안 락을 대기할 시간을 지정해서 해당 시간이 지나도 락을 얻지 못하면
false를 반환해서 이후 동일한 프로세스로 이어지도록 할 수도 있습니다
tryLock과 시간설정, 그리고 lock()메소드를 통해 eentranceLock으로 synchrhonized의 단점인
무한대기와 공정성 문제를 해결하면서 더 손쉽고 세밀하게 스레드를 제어할 수 있게 되었습니다!