java.util.concurrent

병행 프로그래밍에서 유용하게 사용하는 패키지

  • Locks : 상호 배제를 사용할 수 있는 클래스 제공
  • Atomic : 동기화가 되어 있는 변수 제공
  • Executors : 쓰레드 풀 생성, 쓰레드 생명 주기 관리, Task 등록과 실행 등을 간편하게 처리
  • Queue : thread-safe한 FIFO queue 제공
  • Synchronizers : 특수한 목적의 동기화를 처리하는 5개의 클래스(Semaphore, CountDownLatch, CyclicBarrier, Phaser, Exchanger)

Locks

java.util.concurrent.locks

locks 패키지엔 상호 배제를 위한 Lock API가 정의 되어 있다.

synchronized block을 사용했을 때와 동일한 메커니즘으로 동작한다.

내부적으로 synchronized를 사용해 구현되었으며, synchronized를 더욱 유연하고 정교하게 처리 하기 위해 사용하는 것이지 대체하는 목적을 가지진 않았다.

package java.util.concurrent.locks;

import java.util.concurrent.TimeUnit;

public interface Lock {
    void lock();

    void lockInterruptibly() throws InterruptedException;

    boolean tryLock();

    boolean tryLock(long var1, TimeUnit var3) throws InterruptedException;

    void unlock();

    Condition newCondition();
}
public interface ReadWriteLock {
    Lock readLock();

    Lock writeLock();
}
  • lock() : Lock 인스턴스에 잠금을 걸어둔다. Lock 인스턴스가 잠겨있는 상태면 잠금을 걸어둔 쓰레드가 unlock()을 호출할 때까지 실행이 비활성화 된다.

Synchronized vs Lock

synchronized와 큰 차이 없어 보이고, 대체 가능해 보인다. 그렇다면 무슨 차이가 있는 걸까?

fairness 모든 쓰레드가 자신의 작업을 수행할 기회를 공평하게 갖는 것을 의미한다. 공정한 방법에선 큐 안에 쓰레드들이 무조건 순서를 지켜가며 lock을 확보한다. 불공정한 방법에선 만약 특정 쓰레드에 lock이 필요한 순간 release가 발생하면 대기열을 건너뛰는 새치기 같은 일이 벌어진다.

starvation 다른 쓰레드들에게 우선순위가 밀려 자원을 계속해서 할당받지 못하는 상황

synchronized는 공정성을 지원하지 않는다. 반면 ReentrantLock은 생성자의 인자를 통해서 공정/불공정 설정을 할 수 있다.

public ReentrantLock() {
        this.sync = new ReentrantLock.NonfairSync();
    }

    public ReentrantLock(boolean fair) {
        this.sync = (ReentrantLock.Sync)(fair ? new ReentrantLock.FairSync() : new ReentrantLock.NonfairSync());
    }
...
}
  • synchronized는 블록 구조이기 때문에 메소드 안에서 임계 영역의 시작과 끝이 있어야 한다. 그러나 Lock은 lock(), unlock()을 통해 시작과 끝을 명시하기 때문에 임계 영역을 여러 메서드에 나눠 작성 가능하다.
  • synchronized는 race-condition시 누가 진입할지 알 수 없다(implicit lock). 하지만 Lock은 누가 진입할 수 있지 순서를 지정할 수 있다.(explicit lock)

CountDownLatch

하나 이상의 스레드가 다른 스레드에서 수행되는 작업들이 완료될 때까지 대기할 수 있도록 하는 동기화 장치

참고 자료