날짜별 글 목록: 2010년 4월 29일

스레드-안전(Thread-safe)과 재진입가능(Reentrant)의 차이

다중 스레드 프로그래밍을 하다 보면, 스레드-안전(Thread-safe)과 재진입가능(Reentrant)이란 용어를 보게 된다. 처음에는 이 두 용어에 대한 개념이 명확하지 않게 느껴지지만, 명확한 차이가 있다. 우선 이 두 개념에 대한 정의를 살펴보자.

1. 스레드-안전(Thread-safe) 정의
스레드 안전은 다중 스레드가 동시에 같은 로직을 실행하는 경우에도 결과가 정확함을 보장한다는 것이다. 따라서 여러 스레드가 클래스에 접근할 때, 실행 환경이 해당 스레드들의 실행을 어떻게 스케줄 하든 어디에 끼워 넣든, 호출하는 쪽에서 추가적인 동기화나 다른 조율 없이도 정확하게 동작하면 해당 클래스는 스레드 안전하다고 말한다.

2. 재진입가능(Reentrant) 정의
재진입가능은 다중 스레드가 동시에 같은 로직을 실행할 수 있도록 구현해서 스레드-안전을 확보하는 형태이다.

위에서 정의한 내용을 토대로, 재진입가능한 로직은 스레드-안전을 확보해서, 스레드-안전을 확보하는 하나의 수단이 된다. 하지만 스레드-안전을 확보하는 수단은 재진입가능한 로직외에도 여러 동기화 기법이 있다.

자바에서 스레드-안전한 로직을 구현하는 방법은 다음과 같다.
– Atomic 시리즈의 클래스 사용
– 뮤텍스 사용
– 세마포어 사용
– 아래의 재진입가능한 로직을 구현한 형태 사용

자바에서 재진입가능한 로직을 구현한 형태는 다음과 같다.
– 암시적인 락 : 기본적으로 자바 객체는 모니터 락을 가지고 있고, 이것으로 재진입가능한 로직의 형태로 사용할 수 있다.
– ReenterantLock과 ReentrantReadWriteLock의 사용

* Reference
– 자바 병렬 프로그래밍

싱글톤 패턴(Singleton Pattern)

아래는 싱글톤(Singleton) 패턴을 구현하는 몇 가지 방법이고, 개별 방법이 가지는 문제에 대해서 살펴보자.

1. EagerSingleton

static class EagerSingleton extends Singleton { 
    static final EagerSingleton theInstance = new EagerSingleton(); 
    static EagerSingleton getInstance() { 
        return theInstance; 
    } 
}

이 경우는 미리 싱글톤 인스턴스를 생성하는 방법으로 static final 필드에 인스턴스를 생성하여 할당하는 방법이다. 이 필드는 클래스로더에 의해서 EagerSingleton이 메모리로 올라오는 순간에 안전하게(thread-safe하게)초기화된다. 이 방법이 성능이 가장 좋을 것이다.
– 장점
동기화부담이 적다는 것이다. 클래스로더가 클래스를 로드하는 시점에 로드되서 안전하다.
– 단점
인스턴스를 미리 생성한다는 것이다.

2. SynchedSingleton

static class SynchedSingleton extends Singleton {
  static SynchedSingleton theInstance;
  static synchronized SynchedSingleton getInstance() {
    if (theInstance == null) {
      theInstance = new SynchedSingleton();
    }
    
    return theInstance;
  }
}

이 방법은 싱글톤 객체에 접근하는 메서드를 동기화하는 방법이다. 매번 동기화를 하기에 자바 메모리 모델에 따른 각종 문제를 방지할 수 있어서 스레드에 안전(thread-safe)하다.
– 단점
매번 동기화를 수행해서 수행 비용이 높다.

3. ThreadLocalSingleton

static class ThreadLocalSingleton extends Singleton {
  static final ThreadLocal perThreadInstance = new ThreadLocal();
  static final Object lock = new Object();
  static ThreadLocalSingleton theInstance;
  static ThreadLocalSingleton getInstance() {
    ThreadLocalSingleton instance = (ThreadLocalSingleton)(perThreadInstance.get());
    if (instance == null) {
      synchronized(lock) {
        instance = theInstance;
        if (instance == null) {
          instance = theInstance = new ThreadLocalSingleton();
        }
        
      } // copy global to per-thread
      perThreadInstance.set(instance);
    }
    return instance;
  }
}

이 방법은 Thread Local Storage를 사용한다. 자바 스레드 메모리 모델에서 문제가 발생하는 것은 멀티 CPU상황에서 각각의 CPU가 자신의 캐시내에 클래스의 필드를 복사해넣는다는 것입니다. 이런 캐시는 메인메모리와 동기화가 되어 있지 않다. 즉, 각각의 CPU가 하나의 클래스를 접근하게되면 각각의 CPU는 동일 클래스에서 다른 값을 가져올 수 있게된다. 싱글톤을 구현하면 instance라는 필드를 여러개의 CPU가 참조하는데 이 필드를 여러 CPU가 다른 값으로 처리한다는 것이다. 예를들면, CPU A가 인스턴스를 생성하고 생성한 인스턴스를 instance에 할당한다고 해 보자. 다음으로 CPU A가 또다시 이 instance 필드를 참조할 때는 생성된 인스턴스를 보게 된다. 그러나 CPU B가 이 instance필드를 참조할때는 instance필드가 null로 보여질 수 있다. 그 이유는 CPU A가 수행한 작업은 synchronized블록을 통과할때까지 메인 메모리에 반영이 안되고 자신의 캐시에만 담겨 있을 수 있고, CPU B역시 synchornized블록을 통과하지 않으면 메인메모리가 아닌 자신의 캐시만 확인하기 때문이다.

이를 해결하려면 개별 CPU가 동기화 블록을 진입하고 나와야 하는데, 이를 구현한 것이 위 코드입니다. 각각의 Thread는 자신만의 메모리공간으로 TLS(Thread Local Storage)를 가지고 있고, TLS는 개별 스레드가 가지는 공간으로 동기화가 필요 없다. 따라서 이 저장소에 해당 스레드가 synchronized블록을 한번이라도 다녀왔는지(한번이라도 다녀오면 CPU가 메인메모리의 값을 가져온다)를 저장한다.

4. SimulatedThreadLocalSingleton

static class SimulatedThreadLocalSingleton extends Singleton {
  static SimulatedThreadLocalSingleton theInstance;
  static final Object lock = new Object();
  static final Object key = new Object();
  static Singleton getInstance() {
    TSS t = (TSS)(Thread.currentThread());
    Singleton instance = (Singleton)(t.threadLocalHashtable.get(key));
    
    if (instance == null) {
      synchronized(lock) {
        instance = theInstance;
        if (instance == null)
          instance = theInstance = new SimulatedThreadLocalSingleton();
      } // copy global to per-thread
      t.threadLocalHashtable.put(key, instance);
    }
    return instance;
  }
}

이 방법은 ThreadLocal 클래스를 쓰지 않고 TLS를 직접 구현한 방식입니다.

5. VolatileSingleton

static class VolatileSingleton extends Singleton {
  static final Object lock = new Object();
  static volatile VolatileSingleton theInstance;
  static VolatileSingleton getInstance() {
    VolatileSingleton instance = theInstance;
    if (instance == null) {
      synchronized(lock) {
        instance = theInstance;
        if (instance == null) {
          instance = theInstance = new VolatileSingleton();
        }
      }
    }
    return instance;
  }
}

주의!)이 방법은 사용하면 안 된다.

이 방법은 volatile를 사용한다. volatile로 선언된 필드는 매번 원자적으로 스레드 안전하게 동작한다. 즉 개별 변수에 대한 접근이 매번 메인메모리와 동기화 되기에 스레드 안전하게 동작한다. synchronized와 volatile은 이처럼 변수의 접근마다 동기화를 하느냐 아니면 특정 블록을 통채로 동기화 하는냐의 문제에 대한 접근방법이다. 그러나 아쉽게도 volatile은 대부분의 자바 컴파일러에서 제대로 구현되어있지않으며 따라서 사용하는것을 권하지 않는다.

volatile은 동기화의 문제를 비롯한 다양한 암시적 작동이 보장되어야하는데 이를 제대로 책임지고 않기 때문이다.

6. DirectThreadFieldSingleton

static class DirectThreadFieldSingleton extends Singleton {
  static DirectThreadFieldSingleton theInstance;
  static final Object lock = new Object();
  static Singleton getInstance(TSS t) {
  Singleton instance = t.singleton;
  
  if (instance == null) {
    synchronized(lock) {
      instance = theInstance;
      if (instance == null) {
        instance = theInstance = new DirectThreadFieldSingleton();
      }
    } // copy global to per-thread
    t.singleton = instance;
  }
  
  return instance;
  }
}

인스턴스를 할당받고자하는 쪽에서 TSS라는 형태의 클래스를 스레드마다 할당한채로 갖고 있다가 이것을 싱글톤 클래스에 넘깁니다. 그러면 TSS.singleton변수의 값을가지고 동기화 수행여부를 결정하는 방식이다. ThreadLocal의 변형이며, 모든 인스턴스를 획득하고자하는 스레드가 TSS를 넘겨야한다는 점에서 좋은 방법은 아니다.

7. ThreadFieldSingleton

static class ThreadFieldSingleton extends Singleton {
  static final Object lock = new Object();
  static ThreadFieldSingleton theInstance;
  static Singleton getInstance() {
    TSS t = (TSS)(Thread.currentThread());
    Singleton instance = t.singleton;
    if (instance == null) {
      synchronized(lock) {
        instance = theInstance;
        
        if (instance == null) {
          instance = theInstance = new ThreadFieldSingleton();
        }
      } // copy global to per-thread
      
      t.singleton = instance;
    }

    return instance;
  }
}

이 방법 또한 ThreadLocal 구현에 대한 변형된 구현입니다. ThreadLocal 대신, 특정 싱글톤 클래스에 대한 인스턴스를 획득하려고 시도하는 스레드를 캐스팅해서 ThreadLocal을 구현한 것이다. 개인적으로 이 방법도 좋지 않다고 본다. 특정 클래스의 인스턴스를 획득하는데 스레드가 특정 인스턴스를 구현하고 있는지는 쉽게 가정하기 어렵기 때문이다.

가비지 컬렉터(garbage collector)

자바의 가비지 컬렉터(Garbage Collector) 이슈는 성능과 더불어 자원 해제라는 막중한 역할을 하기 때문에 중요하게 파악을 해 둬야 되는 내용입니다. 아래는 자바에서 지원하고 있는 가비지 컬렉터의 영역별 내용과 형태에 대한 내용이다.

– serial collector : young, old 영역, java 1.4 버전까지 default collector로 사용, single cpu만 사용
– parallel collector : young 영역, java 5 버전부터 CPU 2, MEM 2G 이상(Hot Spot VM
)일 경우 자동선택
– parallel compacting collector : old 영역, option으로 지정해야 선택됨
– cms(concurrent mark-sweep) collector : old 영역, option으로 지정해야 선택됨
– G1 collector: young, old 영역,  1.6.0_14(NUMA 구조 지원, 1.6.0_18 버전에 성능에 대한 언급)부터 사용이 가능하다. option으로 지정해야 선택됨. “early access software”, 자바 비즈니스 라이선스를 구매하지 않은 고객은 사용을 하지 않는 것이 좋다고 한다. Java 7버전을 타켓으로 하고 있다고 한다.

자바 C/C++, C# 메서드 호출 지원 라이브러리

자바에서 C/C++, 그리고 C#의 메서드를  호출(Interop)할 수 있게 지원하는 라이브러리들이다.

1. C/C++ 메서드 호출

기본으로 자바에서 제공하는 JNI를 사용해서 C/C++의 메서드를 호출한다. 그리고, 아래는 JNI를 쉽게 사용하도록 래퍼(Wrapper)를 제공하는 라이브러리들이다.
– HawtJNI(https://github.com/fusesource/hawtjni)
– JNIWrapper(http://www.teamdev.com/jniwrapper/)
– JNIEasy(http://www.innowhere.com/jnieasy/?st=jnieasy_products#!st=jnieasy_products)

2. C# 메서드 호출

j-interop(Pure Java – Com Bridge) : http://j-interop.dimentrix.com/, 라이센스 : LGPL 3.0[1]

3. 자바 C# 양뱡 호출

– Jni4net : http://jni4net.sourceforge.net/, bridge between Java and .NET (interprocess, fast, object oriented, open-source), 라이선스: opensource, GPL tools and LGPL runtime

using net.sf.jni4net;

public class Program
{
	private static void Main()
	{
		Bridge.CreateJVM(new BridgeSetup());
		java.lang.System.@out.println("Greetings from C# to Java world!");
	}
}

C# 예제

import net.sf.jni4net.Bridge;
import java.io.IOException;
import java.lang.String;

public class Program {
	public static void main(String[] args) throws IOException {
		Bridge.init();
		system.Console.WriteLine("Greetings from Java to .NET world!");
	}
}

자바 예제

– ikvm.net : http://www.ikvm.net/, jar -> .dll, .dll -> .jar

소프트웨어 관리자의 개선 우선순위

자세한 내용은 https://www.ibm.com/developerworks/kr/library/dwclm/20100427/index.html를 참고하시면 됩니다.  내용에서 소프트웨어 관리자의 관리개선 효과가 프로젝트에 미치는 영향이 최고입니다. 하지만, 프로젝트 관리자는 주로 효과가 미약한 툴 개선을 주로 해 볼려고 한다는 것이죠..
참 공감이 가는 얘기입니다.. 개인적으로는 절대절대, 개발을 잘 해온 사람이 제 위의 관리자가 되었으면 합니다.

  • 도구: 2.97배
  • 사람: 10.55배
  • 시스템: 25.76배
  • 관리: 64.00배

마지막의 소프트웨어 개발 관리자는 어떤 소스 컨트롤 툴을 사용할지 고심하는 것 외에도 노력해야 할, 중요한 것들이 있다. 그것은 자신을 돌아보고 관리 방식 자체에 문제가 없는지 살펴보고 개선하는 것이다. 관리하시는 분들, 툴이 어쩌구 저쩌구가 아니라 관리 좀 잘해주소..