'atomic'에 해당되는 글 1건

  1. 2008.11.12 Atomic 변수

Atomic 변수

java 2008. 11. 12. 15:07

JDK 5.0에서 지원되는 새로운 동시성 유틸리티에 대해서 지난 3개의 테크팁에서 다루었다. 2005년 4월 12일자 테크팁 SYNCHRONIZER 배우기에서는 동기화에 대해서, 2004년 12월 7일자 테크팁 일시적 태스크 실행시에 쓰레드 풀링 이용하기에서는 쓰레드 풀(pool)에 대해, 2004년 11월 4일자 테크팁 QUEUE와 DELAYED 프로세싱에선 블로킹 queue를 포함하여 동시발생 콜렉션에 대해 알아보았다. 이번 테크팁에서는 JDK 5.0에서 지원되는 동시성의 또다른 면에 대해 알아보도록 하자. 이는 atomic 변수이다.

서로 다른 쓰레드 간에 변수를 공유하는 것에 대한 위험을 알고있을 것이다. 공유 변수에 대한 액세스를 제한하여 한번에 한 쓰레드만이 공유 변수를 변경하는 것을 확실히하는 것이 중요하다. 이는 한번에 하나의 쓰레드만 보호된 섹션에 있게 하는 등 일반적으로 동기화 블록을 통해 중요한 코드 섹션을 래핑하여 제한한다.

이 기술은 잘 먹히지만 동기화 코드는 프로그램에 런타임 비용을 추가하게 된다. 동기화된 락(lock)을 얻고, 변수를 수정하고, 락을 푸는데는 시간이 걸린다. 간단한 변수 변경시에는 락을 사용하지 않는다던가, 간단히 처음부터 락 없는 알고리즘을 가지면 훨씬 시간이 절약될 것이다. 그러나 다른 것으로 대체하지 않는 채 동기화 블록을 제거할 수는 없다.

JDK 5.0에서는 새로운 java.util.concurrent.atomic 패키지를 통해 이런 사항을 충족시킨다. 이 패키지 안의 클래스들은 지정된 타입의 변수들에 원자적으로 액세스할 수 있게 한다. 또한 원자적get-and-set 타입의 연산을 위한 메소드를 제공한다.

이 패키지에는 Integer 값을 원자적으로 업데이트하기 위한 AtomicInteger 클래스, long 값을 원자적으로 업데이트하기 위한 AtomicLong, 기본적인 boolean 연산을 위한 AtomicBoolean, 원자 오브젝트 비교/세팅을 위한 AtomicReference가 포함되어 있다. 또한 배열을 특별히 핸들링하기 위한 클래스들도 있다. AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray가 그것이다.

Atomic 변수를 시작하기 위해 AtomicInteger 클래스를 살펴보자. 본질적으로 이 클래스는 래핑된 Integer 값과 같은 일을 한다. 그 클래스의 get() 메소드로 값을 얻어서 set() 메소드로 이를 세팅한다. getAndSet() 메소드로 값을 얻고 설정하는 것을 한번에 해결할 수도 있다. 이를 통해 값을 얻기 위한 호출과 설정을 위한 호출 사이에 다른 쓰레드가 값을 변경하는 위험을 없앨 수 있다.

Integer에서의 기본적인 get/set 연산은 다음과 같다. myVariable이 변수로 사용된다.

   // Save off old value
int oldValue = myVariable;

// Change to new value
myVariable = oldValue + 1;

동기화된 블록에 이 코드들을 삽입하지 않는다면 두 선언문의 중간에 쓰레드 스케줄러가 끼어들 가능성이 있다. 그렇게 되면, myVariable의 수정된 값이 아닌 원본값에 myVariable이 변경되버린다. 이는 ++ auto-increment 연산자를 사용할 때도 이와 비슷한 문제점이 나타난다. 동기화된 블록에서 ++을 사용할 경우 auto-increment 연산이 원자적으로 동작하는 것을 보장할 방법이 없다.

이 문제를 예방하기 위해, AtomicInteger에서의 새로운 atomic 메소드 중 하나를 사용하기 바란다. 이 메소드는 get연산이나 set 연산을 여러 개의 다른 메소드 중 하나로 결합시킨다.

  • addAndGet()
  • getAndAdd()
  • decrementAndGet()
  • getAndDecrement()
  • incrementAndGet()
  • getAndIncrement()
  • getAndSet()

왜 이 메소드들은 대부분 두개의 버전이 있는가? "get"이 먼저이면 리턴된 값은 원래 값이다. "get"이 나중이면 리턴된 값은 새롭게 조정된 값이다. 그러므로 10을 값을 가진 AtomicInteger에서는 getAndIncrement()는 10을 리턴, incrementAndGet()은 11을 리턴하게 된다. 두가지 경우 모두, 호출 이후 AtomicInteger는 11이다.

AtomicInteger에서 언급할만한 또다른 메소드는 compareAndSet(int expect, int update)이다. 이 메소드는 AtomicInteger의 값이 올바른 값인지 확인한다음, 올바른 값이면 AtomicInteger를 새롭게 업데이트된 값으로 변경한다. 실제로, 앞에서 언급된 거의 모든 메소드가 내부적으로 compareAndSet()으로 구현된다.

Atomic 패키지에서의 클래스의 값을 보려면 다음을 고려해야한다. 동기화된 setter/getter 메소드로 보호되고 있는 속성이 있다고 하자

  public class MyLong1 {
private long seed;
public synchronized void setSeed(long seed) {
this.seed = seed;
}
public synchronized long getSeed() {
return seed;
}
}

AtomicLongAtomicLong 관련 클래스를 사용하여 이를 비동기화 버전을 사용하는 것으로 변경할 수 있다.

  public class MyLong2 {
private AtomicLong seed;
public void setSeed(long seed) {
this.seed.set(seed);
}
public long getSeed() {
return seed.get();
}
}

Setter 메소드의 대입문이 AtomicLongset() 메소드 호출로 변경되고 getter 메소드가 AtomicLongget()를 호출한다는 것에 주의하자. 여기서 AtomicLong을 사용하여 메소드 동기화를 제거했다. java.util.Random 클래스의 경우, 메소드의 다른 면들 때문에 setSeed() 메소드는 여전히 동기화된다.

간단한 법칙으로, int, long, or boolean 타입의 변수들에 액세스하기 위해 동기화 블록을 생성했다면 동기화 블록을 atomic 변수와 교환할 것을 고려하기 바란다. 좀 더 복잡한 타입을 위해서는, AtomicReference의 도움으로 사용자 각자의 동기화 타입을 생성해보기 바란다.

atomic 패키지에 대한 좀 더 자세한 정보는 java.util.concurrent.atomic의 javadoc을 참조하기 바란다.

Posted by foryamu
,