현장에서 ThreadPool을 사용할 때 가장 많이 사용하는 것이 Spring에서 내장하고 있는 클래스들이다.

그런데 이 클래스들을 뜯어서 보니 Java에서 기본으로 제공하는 interface나 class와는 구조나 모임의 단위가 조금 달라보인다. google을 아무리 찾아봐도 만족할 만한 답이 없어서 시간이 나는동안 소스를 열어봤다.

가장 많이 사용하는 클래스들을 뽑아서 diagram을 그려봤다.

왼쪽에 Spring, 오른쪽이  Java이다.


1. Java

Java의 Concurrent 패키지에서는 순수하게 기능에 집중해서 작성한 것으로 보인다.

쉽게 가져가서 사용할 수 있는 구조를 위해서 많이 노력한 것으로 보이며 ISP의 원칙을 잘 지킨 것 같다.


a. Executor : Runnable 구동이 가능하며 execute 메소드를 사용한다.

b. ExecutorService : Executor를 상속받았다. Callable 구동이 가능하며 추가적으로 submit 메소드를 제공한다.

ThreadPool의 lifecycle에 관련된 메소드도 보인다.

c. ThreadPoolExecutor : 실제 개발자가 ThreadPool 사용을 위해서는 ThreadPoolExecutor를 생성해서 사용하며,

이를 좀 더 쉽게 사용할 수 있도록 Util성클래스인 Executors 를 제공한다.

d. ScheduledExecutorService : Schedule작업을 위한 인터페이스로 schedule 메소드가 정의되어 있다.



2. Spring

Spring의 경우에는 좀더 사용자의 측면에 접근해서 설계가 되어있는 모습이다.

내부구현은 Java에서 제공하는 class들을 멤버변수로 가지고 있어서 대부분 처리를 진행하고,

사용자가 좀더 편리하게 사용할 수 있도록 다양한 설정포인트를 제공한다.

Thread작업에 task라는 개념을 도입하여 재구성한 형태로 그 출발은 TaskExecutor이다.


a. TaskExecutor : java의 Executor와 완전히 동일하다. Spring에서의 확장을 위해서 만들었다.

b. SyncTaskExecutor : Runnable의 run메소드를 호출만 한다.

c. AsyncTaskExecutor : Runnable과 Callable의 구동이 가능하며, execute 메소드와 submit 메소드를 제공한다.

d. TaskScheduler : Schedule작업을 위한 인터페이스로 scheduler 메소드가 정의되어 있다.


이러한 각 인터페이스를 용도에 맞게 구현하여 아래의 클래스 형태들로 제공하고 사용자는 자신의 목적에 맞도록 선택하여 사용한다.


또한 Spring에서는 Future의 확장을 위해서 ListenableFuture를 별도로 정의하여 사용하고 있다.

이부분은 다음에 시간날때 보도록 해야겠다...



<개요>

volatile은 컴파일러가 특정변수에 대해서 옵티마이져에 대해서 캐슁을 하지 않고, 리오더링을 하지 않도록 해주는 키워드라고 한다.

그로 인해 멀티쓰레드 환경에서 동기화문제를 만날 때 사용하는 경우가 많다.

 

<내용>

예를 살펴보자

기본적으로 자바기본형은 thread-safe하지만 개발환경에 따라 특이한 경우가 발생할 수 있다.

32bit환경에서 long이나 double처럼 64bit 값을 처리할 때 assign이 두단계에 걸쳐서 일어나게 된다. 

(address의 한계때문에 발생한다.


volatile키워드를 사용할 경우 이에 대한 원자성을 보장하여 오류를 방지해준다.


그렇다면 synchronized 는 언제 사용할까? 위와 같이 volatile로 선언하더라도

public void add(int i){

    longResult += i;

}

와 같은 메소드를 수행하면 + 연산까지 원자성을 보장하지는 않기 때문에 마찬가지로 오류가 발생할 수 있다.


이때 add 메소드에 대해서 synchronized 키워드를 사용하여 동기화를 적용한다.


하지만 해보신분들은 알겠지만 synchronized를 사용하는 순간 엄청나게 느려진다. 어찌보면 당연한 결과이다...

 

<정리>

이를 보완하기 위해서 JDK 1.5이후에서는 Atomic 타입을 제공

Class Description
AtomicBoolean
A boolean value that may be updated atomically.
AtomicInteger
An int value that may be updated atomically.
AtomicIntegerArray
An int array in which elements may be updated atomically.
AtomicIntegerFieldUpdater<T>
A reflection-based utility that enables atomic updates to designated volatile int fields of designated classes.
AtomicLong
A long value that may be updated atomically.
AtomicLongArray
A long array in which elements may be updated atomically.
AtomicLongFieldUpdater<T>
A reflection-based utility that enables atomic updates to designated volatile long fields of designated classes.
AtomicMarkableReference<V>
An AtomicMarkableReference maintains an object reference along with a mark bit, that can be updated atomically.
AtomicReference<V>
An object reference that may be updated atomically.
AtomicReferenceArray<E>
An array of object references in which elements may be updated atomically.
AtomicReferenceFieldUpdater<T,V>
A reflection-based utility that enables atomic updates to designated volatile reference fields of designated classes.
AtomicStampedReference<V>
An AtomicStampedReference maintains an object reference along with an integer "stamp", that can be updated atomically.

public class AtomicLong extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 1927816293512124184L;

    // setup to use Unsafe.compareAndSwapLong for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

<중략>

private volatile long value;  /**
     * Creates a new AtomicLong with the given initial value.
     *
     * @param initialValue the initial value
     */
    public AtomicLong(long initialValue) {
        value = initialValue;
    }

/**
     * Atomically adds the given value to the current value.
     *
     * @param delta the value to add
     * @return the previous value
     */
    public final long getAndAdd(long delta) {
        return unsafe.getAndAddLong(this, valueOffset, delta);
    }

 a. 예를 들면Long대신에 AtomictLong을 사용하고 제공되는 addAndGet메소드를 사용할 경우 멀티쓰레드 간의 값을 보장하면서 속도도 매우 빨라진다.


 b. 내부적으로 코드를 살펴보면 AtomicLong은 Number를 확장하며 Serializable을 impl한다 (어찌보면 당연하다..)

 c. 멤버변수로 private volatile long value를 가지고 있으며 addAndGet메소드는 final로 선언되어 무한루프를 반복한다.

 d. Unsafe class의 compareAndSwapLong 메소드를 활용한 compareAndSet메소드를 사용한다. 모든 연산메소드가 동일하다.

 

<참고사이트>

https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/package-summary.html

불변객체라고 부른다. Java에만 있는 개념은 아니고, 일반적으로 왠만한 언어에는 다 있는 개념이다.

간단히 설명하면 immutable object는 최초에 생성된 뒤 변화할 수 없는 객체이다.

대조적으로 mutable object는 수정이 가능하다.


String s = "ABCDEC";

s.toLowerCase();

toLowerCase는 "ABCDE"를 바꾸는 것이 아니라 "abcde"라는 새로운 String 객체를 생성한다.

(jdk소스를 보면  return new String(result, 0, len + resultOffset); 와 같이 되어있다.)


유사한 immutable로는 Integer, Long, Short, Double, Float, Character, Byte, Boolean등의 Primitive wrapper가 있다. 이러한 immutable object들은 thread-safe와 보안의 목적으로 자주 사용된다.


인위적으로 불변객체를 만들어서 값의 변경이 불가능하도록 하는 것도 가능하지만 생각보다 귀찮은 방법이다. 

(방어복사 : defensive copying)

간략하게 설명하면 class와 멤버변수를 final로 정의하고, setter메소드를 없앤다.

또한 getter메소드에서 값을 반환할 때 clone을 생성하여 반환하도록 한다.

(만약에 멤버변수가 객체라면 해당객체를 deep copy해야 보다 완벽할 듯)


immutable object에는 많은 장점이 있다.

- 기본적으로 thread safe하고

- 동기화작업에서 이점이 있다. (lock을 걸 필요가 없다.)

- 또한 안정한 공유,재사용이 되기 때문에 cache를 사용하여 성능향상 및 메모리 효율을 도모한다.

- 이러한 공유는 복잡도를 감소시킨다. O(n) => O(1)


다만 대량 작업을 수행할때 몇가지 속도에 단점이 있어서 튜닝이 필요한 부분들이 있었다.

- concatenation연산이 새로운 객체생성으로 인한 비용이 비싼편이다. 물론 1.6이후였나? StringBuilder를 사용하기 떄문에 대부분 해소가 된다.

- 반복적인 new copy의 경우 메모리 소모가 매우 증가한다. 100만번 loop문을 수행하면 @.@


내가 수행하고자 하는 업무의 특성에 따라 맞게 사용하면 될 듯한데, WAS상의 온라인업무에서는 적극 활용되는 모습니다.


참고(http://stackoverflow.com/questions/9544182/why-are-strings-immutable-in-many-programming-languages)


1. 공통점과 차이점 비교

공통점

- 인스턴스화 할 수 없다.

- 상세구현없이 메소드를 정의한다.


차이점

- Interface의 경우 public만 가능하다.

- Interface의 경우 동시에 여러개 사용이 가능하지만, Abstract Class의 경우 하나만 사용가능하다.


2. 적용되는 유형

Abstract Class

- 연관되는 클래스들이 공통되는 코드를 포함하고 있을 때

- public외의 접근자 사용이 필요할 때

Interfaces

- 연관성없는 클래스들이 각기 구현이 필요할 경우

- 상세구현내용에 대해서는 몰라도 될때


3. 정리

- Interface의 경우 제공되는 기능을 중심으로 정리되는 듯 하다. 

  ex) JDBCOperation, ItemOperation

- Abstract의 경우 추상화를 통한 공통변수, 메소드 정리에 사용된다. 

  ex) Graphic Object (Line, Circle, Rectangle)

- Interface와 Abstract 동시사용이 가능하다. 일반적으로 팩토리 패턴에서 많이 사용된다.


인터페이스의 경우 원격호출을 통해서 사용할 때 상대에게 방식을 알려주는 용도로 많이 사용된다.

따라서 한번 정의되면 변경이 쉽지 않기 때문에 처음 정의시 고민이 필요하다.

Abstract의 경우 기본구현을 통해서 뼈대를 만들고 각 상세메소드는 상속클래스에서 정의하는 방식을 많이 사용한다.

이 때 상위클래스의 빈 메소드는 Abstract Method로 정의해주는 것이 혼란을 피할 수 있다.


+ Recent posts