이 글은 원 저자 Jakob Jenkov의 허가로 포스팅된 번역물이다.

원문 URL : http://tutorials.jenkov.com/java-concurrency/compare-and-swap.html



컴페어 스왑은 동시성 알고리즘을 설계할 때 사용되는 기술이다. 기본적으로 컴페어 스왑은 변수의 예상 값과 실제 값을 비교하여 둘의 값이 일치하면 실제 값을 새로운 값으로 교체한다. 약간 복잡하게 들릴 수 있지만 사실 한 번 이해하면 그리 어렵지 않다. 자세히 다가가보자.



컴페어 스왑은 어디에 쓰이는가


여러 프로그램들과 동시성 알고리즘에서 대단히 널리 발견되는 패턴은 '체크-액트' 이다. 체크-액트 패턴은 변수의 값을 확인하여 이 값을 전제로 다음 동작을 수행한다. 


class MyLock {

    private boolean locked = false;

    public boolean lock() {
        if(!locked) {
            locked = true;
            return true;
        }
        return false;
    }
}


실제 멀트쓰레드 어플리케이션에서 이 코드를 사용하려면 많은 문제가 있겠지만 여기서는 넘어가도록 하자.


코드에서 보이듯 lock() 메소드는 먼저 locked 멤버변수의 값을 확인하여 값이 false 라면(체크) 값을 locked 을 true 로 바꾸고 true 를 반환한다(액트).


다수의 쓰레드가 같은 MyLock 인스턴스에 접근한다면 lock() 메소드는 제대로 동작하지 않을 것이다. 쓰레드A 가 변수 locked 의 값이 false 인 것을 확인했다. 그리고 그와 동시와 쓰레드B 가 똑같이 변수 locked 의 값이 false 인 것을 확인했다. 따라서 쓰레드A 와 쓰레드B 모두 locked 를 false 로 확인했고, 두 쓰레드 모두 이 값을 전제로 다음 동작을 수행할 것이다.


멀티쓰레드 어플리케이션에서 '체크-액트' 연산이 제대로 수행되려면 연산의 원자성이 필요하다. 여기서의 원자성이란 '체크' 와 '액트' 의 동작이 분리되지 않은 하나의 원자적인 코드 블록으로 실행되어야 함을 의미한다. 원자적인 코드 블록을 실행하는 쓰레드는 코드 실행의 시작과 끝까지 다른 쓰레드의 간섭을 받지 않는다. 다른 쓰레드들은 원자적인 코드 블록의 실행에 개입할 수 없다(끼어들거나 동시에 실행할 수 없다).


다음 코드는 위 예제의 lock() 메소드에 synchronized 를 사용했다. 이제 lock() 메소드는 원자성을 갖는다.


class MyLock {

    private boolean locked = false;

    public synchronized boolean lock() {
        if(!locked) {
            locked = true;
            return true;
        }
        return false;
    }
}


이제 lock() 메소드는 동기화되었다. 하나의 MyLock 인스턴스의 lock() 메소드는 한 시점에 오직 한 쓰레드만이 실행할 수 있다. lock() 메소드는 사실상 원자적이다.


이 원자적인 lock() 메소드는 실제로 '컴페어 스왑' 의 한 예라고 볼 수 있다. lock() 메소드는 변수 locked 의 예상값 false 로 비교하여 실제 값이 false 이면 변수의 값을 true 로 바꾼다.



원자적 연산으로서의 컴페어 스왑


현대의 CPU는 원자적 컴페어 스왑 연산을 빌트인으로 지원한다. Java 5 부터 java.util.concurrent 패키지의 새로운 원자적 연산 클래스들을 통해 이러한 기능을 사용할 수 있다.


다음 코드는 AtomicBoolean 클래스를 사용하여 lock() 메소드를 구현하였다.


public static class MyLock {
    private AtomicBoolean locked = new AtomicBoolean(false);

    public boolean lock() {
        return locked.compareAndSet(false, true);
    }

}


변수 locked 의 타입은 더이상 boolean 이 아닌, AtomicBoolean 이다. 이 클래스는 AtomicBoolean 인스턴스의 값을 예상값과 비교하여 두 값이 일치하면 실제 값을 새 값으로 세팅하는 compareAndSet() 메소드를 가지고 있다. 예제에서는 AtomicBoolean locked 의 값을 false 로 비교하여 false 가 맞다면 AtomicBoolean locked 의 값을 새 값인 true 로 세팅한다.


compareAndSwap() 메소드는 값이 새 값으로 바뀌었으면 true 를, 바뀌지 않았으면 false 를 반환한다.


컴페어 스왑을 직접 구현하기보다는 Java 5 부터 추가된 컴페어 스왑 기능을 사용하는 것이 좋다. Java 5+ 에 내장된 컴페어 스왑 클래스들은 어플리케이션이 구동되는 CPU의 기본 컴페어 스왑 기능을 사용하기 때문에 보다 빠른 컴페어 스왑 연산이 가능하다.


+ Recent posts