동시성, BlockingQueues(Java 7) - 1

"안녕, 아미고!"

"안녕, 김!"

"오늘은 동시성에 대해 말씀드리겠습니다."

" 동시성은 다중 스레드 작업에 최적화된 특수 클래스를 포함하는 Java 클래스 라이브러리입니다. 이것은 매우 흥미롭고 광범위한 주제입니다. 하지만 오늘은 소개만 하겠습니다. 패키지 이름은 java.util입니다. 동시성 패키지입니다. 몇 가지 흥미로운 클래스에 대해 말씀드리겠습니다."

" 원자 유형. "

"count++도 스레드로부터 안전한 작업이 아니라는 것을 이미 알고 있습니다. 변수가 1씩 증가하면 실제로 세 가지 작업이 발생합니다. 결과적으로 변수가 변경될 때 충돌이 발생할 수 있습니다."

"그래, Ellie는 얼마 전에 나에게 말했다."

스레드 1 스레드 2 결과
register1 = count;
register1++;
count = register1;
register2 = count;
register2++;
count = register2;
register1 = count;
register2 = count;
register2++;
count = register2;
register1++;
count = register1;

"정확합니다. 그런 다음 Java는 이러한 작업을 하나로, 즉 원자적으로(원자는 나눌 수 없음) 수행하기 위해 데이터 유형을 추가했습니다."

"예를 들어 Java에는 AtomicInteger, AtomicBoolean, AtomicDouble 등이 있습니다."

"<카운터> 클래스를 만들어야 한다고 가정해 보겠습니다."

class Counter
{
 private int c = 0;

 public void increment()
 {
  c++;
 }

 public void decrement()
 {
  c--;
 }

 public int value()
 {
  return c;
 }
}

"이 클래스의 개체를 스레드로부터 안전하게 만들려면 어떻게 해야 합니까?"

"음, 모든 메서드를 동기화하고 완료하겠습니다."

class synchronized Counter
{
 private int c = 0;

 public synchronized void increment()
 {
  c++;
 }

 public synchronized void decrement()
 {
  c--;
 }

 public synchronized int value()
 {
  return c;
 }
}

"잘했어. 하지만 원자 유형을 사용하면 어떤 모습일까?"

class AtomicCounter
{
 private AtomicInteger c = new AtomicInteger(0);

 public void increment()
 {
  c.incrementAndGet();
 }

 public void decrement()
 {
  c.decrementAndGet();
 }

 public int value()
 {
  return c.get();
 }
}

"귀하의 클래스와 내 클래스는 모두 같은 방식으로 작동하지만 AtomicInteger가 있는 클래스가 더 빠르게 작동합니다."

"음, 약간의 차이인가요?"

"예. 제 경험에 비추어 볼 때 저는 항상 동기화로 진행하는 것을 권장합니다. 모든 애플리케이션 코드가 작성되고 최적화 프로세스가 시작된 후에야 원자 유형을 사용하도록 코드를 다시 작성하기 시작해야 합니다. 하지만 어쨌든 나는 당신을 원했습니다. 그러한 유형이 존재한다는 것을 알기 위해서입니다. 적극적으로 사용하지 않더라도 이러한 유형이 사용되는 코드에 부딪힐 가능성이 항상 있습니다."

"동의합니다. 말이 됩니다."

"그런데 원자 유형이 불변이 아니라는 사실을 알고 계셨습니까? 표준 Integer 클래스 와 달리 AtomicInteger 에는 내부 상태를 변경하는 메서드가 포함되어 있습니다."

"알겠습니다. StringStringBuffer 와 같습니다 ."

"예, 그런 것입니다."

" 스레드로부터 안전한 컬렉션. "

"이러한 컬렉션의 예로 ConcurrentHashMap을 제시해도 됩니까? HashMap을 스레드로부터 안전하게 만들려면 어떻게 해야 합니까?"

"모든 메서드를 동기화하시겠습니까?"

"물론이죠. 하지만 이제 그러한 SynchronizedHashMap이 하나 있고 여기에 액세스하는 수십 개의 스레드가 있다고 상상해 보세요. 그리고 초당 100번씩 새 항목이 맵에 추가되고 그 과정에서 전체 개체가 읽기 및 쓰기를 위해 잠깁니다."

"음, 이것이 표준 접근 방식입니다. 무엇을 할 수 있습니까?"

"Java의 제작자는 몇 가지 멋진 것을 생각해 냈습니다."

"우선 하나의 블록에 ConcurrentHashMap에 데이터를 저장하지만 '버킷'이라는 부분으로 나눕니다. 그리고 누군가 ConcurrentHashMap에서 데이터를 변경하면 전체 개체가 아닌 액세스되는 버킷만 잠급니다. 다른 즉, 많은 스레드가 개체를 동시에 변경할 수 있습니다."

"두 번째로, 목록/지도의 요소를 반복하면서 동시에 목록을 변경할 수 없다는 것을 기억하십니까? 이러한 코드는 예외를 발생시킵니다."

루프에서 컬렉션의 요소를 반복하면서 동시에 변경하지 마세요.
HashMap<String, Integer> map = new HashMap<String, Integer>();

for (String key: map.keySet())
{
 if (map.get(key) == 0)
  map.remove(key);
}

"하지만 ConcurrentHashMap에서는 다음을 수행할 수 있습니다."

루프에서 컬렉션의 요소를 반복하면서 동시에 변경하지 마십시오."
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<String, Integer>();

for (String key: map.keySet())
{
 if (map.get(key) == 0)
  map.remove(key);
}

"동시 패키지에는 많은 이점이 있습니다. 이러한 클래스를 사용하려면 이러한 클래스를 아주 잘 이해하기만 하면 됩니다."

"그렇군요. 감사합니다. Kim. 정말 흥미로운 수업입니다. 언젠가는 거장처럼 마스터할 수 있기를 바랍니다."