반응형

개인적으로 규칙 3과 규칙 4의 순서가 바뀌는 것이 좋다고 생각함.

 

클래스가 private 생성자만을 가지고 있다면 자신만이 객체를 생성할 수 있기 때문에 규칙 3. 싱글턴 패턴에서 private 생성자를 사용하였다. 또한 싱글턴은 메모리에 올려야 하는 정보(필드)가 있기 때문에 객체를 만들어서 사용한다.

 

싱글턴과는 다르게 메모리에 올려서 사용할 정보들은 없는 Util 클래스들은 객체를 만들 목적의 클래스가 아니다. 따라서 Util 클래스를 작성할 때는 private 생성자를 사용하여 객체 생성을 막는 것이 좋다.

 

아래는 private 생성자를 사용한 Netty의 io.netty.util.internal.StringUtil 소스코드이다.

public final class StringUtil {

    public static final String EMPTY_STRING = "";
    public static final String NEWLINE = SystemPropertyUtil.get("line.separator", "\n");

    public static final char DOUBLE_QUOTE = '\"';
    public static final char COMMA = ',';
    public static final char LINE_FEED = '\n';
    public static final char CARRIAGE_RETURN = '\r';
    public static final char TAB = '\t';
    public static final char SPACE = 0x20;

    private static final String[] BYTE2HEX_PAD = new String[256];
    private static final String[] BYTE2HEX_NOPAD = new String[256];
    private static final byte[] HEX2B;

    ...
    
    // private 생성자를 사용하여 객체 생성을 막는다. 
    private StringUtil() {
        // Unused.
    }

 

private 생성자가 아닌 클래스를 abstract로 선언하여 객체를 생성하지 못하도록 막을 수도 있다. 하지만 해당 클래스는 상속받은 하위 클래스를 만들게 된다면 객체가 결국에 생성되는 것이니 올바른 방법이 아니다.

 

아래는 abstract 클래스로 선언하여 사용한 Spring의 org.springframework.util.StringUtils 소스코드이다.

// abstract로 선언하여 직접적으로 객체는 만들 수 없지만, 상속 받은 하위클래스는 객체 생성이 가능하다.
public abstract class StringUtils {

	private static final String[] EMPTY_STRING_ARRAY = {};

	private static final String FOLDER_SEPARATOR = "/";

	private static final String WINDOWS_FOLDER_SEPARATOR = "\\";

	private static final String TOP_PATH = "..";

	private static final String CURRENT_PATH = ".";

	private static final char EXTENSION_SEPARATOR = '.';
    
    ...

 

/**
 * Spring의 StringUtils는 클래스는 객체 생성을 막기 위해 abstract를 클래스로 만들었지만
 * 하위 클래스가 상속받는다면 객체 생성이 가능
 *
 * @author gwon
 * @history
 *          2021. 10. 24. initial creation
 */
public class ChildSpringStringUtils extends StringUtils {

	public static void main(String[] args) {
		StringUtils stringUtils = new ChildSpringStringUtils();

		stringUtils.isEmpty("abc");
	}
}

 

 

반응형
반응형

싱글턴 패턴은 안티 패턴이라고 주장하는 사람도 많지만 여전히 많은 곳에서 사용되고 있다.

 

싱글턴이란 애플리케이션이 실행 중에 객체를 하나만 가질 수 있는 클래스를 의미한다.

 

싱글턴을 구현하는 방법은 무수히 많으며 이펙티브 자바에서는 세 가지를 설명한다.

 

public static final 변수로 접근 가능한 싱글턴

public class PublicStaticFinalSingleton {
	public static final PublicStaticFinalSingleton INSTANCE = new PublicStaticFinalSingleton();

	private PublicStaticFinalSingleton() {

	}

	public void hello() {
		System.out.println("hello");
	}
}


public class Rule3 {

	public static void main(String[] args) {
		PublicStaticFinalSingleton.INSTANCE.hello();

		new PublicStaticFinalSingleton(); // private 생성자 뿐이므로 컴파일 에러
	}
}

 

getInstance()와 같은 정적 팩토리 메서드로 접근 가능한 싱글턴

public class GetInstanceSingleton {
	private static final GetInstanceSingleton INSTANCE = new GetInstanceSingleton();

	private GetInstanceSingleton() {

	}

	public static GetInstanceSingleton getInstance() {
		return INSTANCE;
	}

	public void hello() {
		System.out.println("hello");
	}
}

public class Rule3 {

	public static void main(String[] args) {
		GetInstanceSingleton.getInstance().hello();

		new GetInstanceSingleton(); // private 생성자 뿐이므로 컴파일 에러
	}
}

 

위 두 방법은 거의 비슷한 방법으로 Eager initialization을 사용한 싱글톤 방식이라고도 한다. 하지만 몇 가지 문제점이 있다.

 

1. 클래스가 로드하게 되면 바로 객체를 생성한다. 만약 해당 싱글톤 클래스가 객체를 생성하는데 많은 시간과 비용이 발생한다면, 애플리케이션이 로드되는데 오래 걸리는 문제가 있을 수 있다. 

 

2. 리플렉션을 사용한다면 생성자가 private여도 호출할 수 있다. 생성자가 두 번 호출되면 예외를 던지도록 처리하여 방어할 수 있다.

 

3. 싱글턴 클래스가 Serializeable이 가능하다면 역직렬화 때마다 새로운 객체가 생성된다. 나중에 포스팅할 내용을 미리 포스팅하여 설명하자면 아래와 같다.

 

/**
 * 싱글턴 클래스는 오직 하나의 객체만 생성되게 하는 클래스이다.
 * 만약 Serializable을 구현한다면 readObject에 의해서 새로운 객체가 만들어지므로 싱글턴 클래스가 아니게 된다.
 *
 * @author gwon
 * @history
 *          2019. 6. 8. initial creation
 */
public class SingletonElvis implements Serializable {
	public static final SingletonElvis INSTANCE = new SingletonElvis();

	private SingletonElvis() {

	}

}

public class Rule77 {

	public static void main(String[] args) throws IOException, ClassNotFoundException {
		// 싱글턴으로 미리 구현된 동일한 객체만 사용 가능
		SingletonElvis elvis = SingletonElvis.INSTANCE;

		// 직렬화 후, 역직렬화
		SingletonElvis newElvis = (SingletonElvis) deserialize(serialize(elvis));

		System.out.println(elvis == newElvis); // false
	}
}

위와 같이 싱글톤을 역직렬 화하면 새로운 객체가 생성되어 객체가 2개 이상이 된다.

 

이를 방어하기 위해서는 아래와 같이 역직렬화 후 호출되는 메서드인 readResolve() 메서드를 항상 동일한 객체를 리턴하도록 작성하면 된다.

/**
 * 이를 막기 위해 readResolve 메서드를 구현하면 싱글턴 속성을 만족하게 오직 하나의 객체만 반환하게 하면 된다.
 *
 * @author gwon
 * @history
 *          2019. 6. 8. initial creation
 */
public class SingletonElvisWithReadResolve implements Serializable {
	public static final SingletonElvisWithReadResolve INSTANCE = new SingletonElvisWithReadResolve();

	private SingletonElvisWithReadResolve() {

	}

	// 역직렬화가 끝난 후, 해당 메서드가 호출되므로 항상 동일한 객체(싱글턴)을 반환하도록 함.
	private Object readResolve() {
		return INSTANCE;
	}

}

 

Enum을 사용한 싱글턴

위에서 언급한 리플렉션, 직렬화를 위한 방어 로직을 작성하기 귀찮다면 Enum을 사용하는 것도 좋은 방법이다.

public enum EnumSingleton {
	INSTANCE;

	public void hello() {
		System.out.println("hello");
	}
}

public class Rule3 {

	public static void main(String[] args) {
		EnumSingleton.INSTANCE.hello();
	}
}

Enum은 직렬화가 자동으로 처리되며, 리플렉션을 통한 공격에도 안전하다.

 

위까지가 이펙티바 자바에서 설명하는 싱글톤 내용이다.

 

Lazy Initialization, Lazy Holder

Eager initialization 싱글톤이 있다면 Lazy initialization 싱글톤도 있다. 소스는 아래와 같다.

public class LazyInitializationSingleton {
	private static LazyInitializationSingleton INSTANCE;

	private LazyInitializationSingleton() {}

	public static LazyInitializationSingleton getInstance() {
		if (INSTANCE == null) {
			INSTANCE = new LazyInitializationSingleton();
		}
		return INSTANCE;
	}

	public void hello() {
		System.out.println("hello");
	}
}

Lazy Initialization은 INSTANCE 필드에 바로 초기화하지 않고 getInstance()를 최초로 호출될 때 객체를 생성하는 방식이다. 따라서 호출되지 않는다면 객체 생성이 미루어지므로 애플리케이션이 로드되는데 부담이 없다.

 

하지만 여러 쓰레드가 동시에 getInstance()를 호출할 경우 객체가 두 번 이상 호출될 수 있는 위험이 존재한다. 이를 방지하기 위해 getInstance()에 synchronized 키워드를 사용하여 해결할 수 있지만, 해당 싱글톤이 자주 사용된다면 효율적이지 않다.

 

따라서 이를 해결하는 방법이 Lazy Holder이다.

 

Lazy Holder는 JVM 클래스 로더의 동작 방식을 이용하여 Lazy Initialization를 만족하면서, 동기화 문제도 해결한다.

https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom

public class LazyHolderSingleton {
	private LazyHolderSingleton() {};

	public static LazyHolderSingleton getInstance() {
		return LazyHolder.INSTANCE;
	}

	private static class LazyHolder {
		private static final LazyHolderSingleton INSTANCE = new LazyHolderSingleton();
	}

	public void hello() {
		System.out.println("hello");
	}
}

Lazy Holder의 원리는 다음과 같다.

 

1. LazyHoladerSingleton은 static 필드가 없기 때문에, 즉 초기화할 것이 없으므로 아주 빠르게 클래스가 로드된다.

2. LazyHolder는 LazyHolder가 실행되기 전까지는 로드가 되지 않는다.

3. 어디선가 LazyHoladerSingleton.getInstance()를 호출하면 LazyHolder를 로드를 수행하고 초기화를 진행한다.

4. LazyHolder가 로드되면서 static 필드인 INSTANCE를 초기화할 때는 JVM 원리상 동시에 수행할 수 없다.

5. 따라서 LazyHolder는 멀티스레드에서도 안전하게 LazyHolderSingleton의 생성자를 호출하여 초기화를 완료한다.

반응형
반응형

규칙 1에서는 생성자 대신 정적 팩토리 메서드를 사용을 고려해보자는 규칙이었다.

 

하지만 생성자 뿐만아니라 정적 팩토리 메서드의 인자가 많을 때는 어떤 위치에 어떤 값이 들어가야 하는지 클라이언트가 하나씩 확인하면서 값을 채워줘야 하기 때문에 사용하기 불편한다.

 

따라서 이를 개선하고자 점진적 생성자 패턴(telescoping constructor pattern)이 만들어졌다.

 

점진적 생성자 패턴

점진적 생성자 패턴이란 필수 인자를 받는 생성자를 하나 정의하고, 선택적 인자를 받는 생성자를 여러개 점진적으로 만드는 방식이다.

 

아래는 Netty의 io.netty.handler.proxy.HttpProxyHandler 예시이다.

public final class HttpProxyHandler extends ProxyHandler {


    private final HttpClientCodecWrapper codecWrapper = new HttpClientCodecWrapper();
    private final String username;
    private final String password;
    private final CharSequence authorization;
    private final HttpHeaders outboundHeaders;
    private final boolean ignoreDefaultPortsInConnectHostHeader;
    private HttpResponseStatus status;
    private HttpHeaders inboundHeaders;

    public HttpProxyHandler(SocketAddress proxyAddress) {
        this(proxyAddress, null);
    }

    public HttpProxyHandler(SocketAddress proxyAddress, HttpHeaders headers) {
        this(proxyAddress, headers, false);
    }

    public HttpProxyHandler(SocketAddress proxyAddress,
                            HttpHeaders headers,
                            boolean ignoreDefaultPortsInConnectHostHeader) {
		...
    }

    public HttpProxyHandler(SocketAddress proxyAddress, String username, String password) {
        this(proxyAddress, username, password, null);
    }

    public HttpProxyHandler(SocketAddress proxyAddress, String username, String password,
                            HttpHeaders headers) {
        this(proxyAddress, username, password, headers, false);
    }

    public HttpProxyHandler(SocketAddress proxyAddress,
                            String username,
                            String password,
                            HttpHeaders headers,
                            boolean ignoreDefaultPortsInConnectHostHeader) {
  		...
    }

 

아래와 같이 생성자가 필요한 인자에 따라서 여러개가 존재하기 때문에 많은 인자가 들어있는 하나의 생성자보다는 사용하기가 편한다.

하지만 만약 해당 클래스의 필드가 추가되게 되면, 생성자를 추가로 생성해야 한다는 문제가 있다.

옵셔널 한 필드라면 생성자가 하나만 추가하게 코드를 작성할 수 있지만, 필수로 입력해야 하는 필드라면 여태 만들어 둔 생성자에 모두 추가해야 하므로 고쳐야 하는 부분이 더욱 많아진다.

 

필드 개수에 따라서 생성자를 추가하는 방법이 아닌 다른 방법은 없을까? 다음 대안인 자바빈 패턴(JavaBeans) 패턴을 보자.

 

자바 빈 패턴

자바 빈 패턴은 한국에서 제일 많이 사용하는 패턴이 아닐까 싶다.

 

자바 빈 패턴은 디폴트 생성자로 객체를 만들고 필드의 setter 메서드로 필요한 값을 하나씩 호출하여 채우는 방식이다.

public class User {
	private String name;
	private int age;
	private String addr;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public void setAddr(String addr) {
		this.addr = addr;
	}
}

public static void main(String[] args) {
	User user = new User();
	user.setName("홍길동");
	user.setAge(10);
	user.setAddr("서울");
}

 

점층적 생성자 패턴보다 복잡하지 않기 때문에 생성하기도 쉬우며, 코드 읽기에도 어려움이 없다.

 

하지만 자바 빈 패턴은 요즘(?) 대세인 클래스 생성 규칙을 위반한다.

 

다른 객체 생성 방식과는 다르게 한 번의 함수 호출로 객체 생성을 끝낼 수 없기 때문에 객체의 일관성이 깨질 수 있다.

하나의 함수로 객체를 생성하게 된다면, 해당 객체의 필드에 값들이 유효한지를 보장하도록 코드를 작성할 수 있지만 각각 필드에 대한 setter에서는 그 유효성을 보장하도록 코드를 작성하기 어렵고, 그렇게 한다고 하더라도 잘못 코드를 작성할 경우, 버그를 디버깅하기 어렵다.

 

또한 setter가 존재한다는 것은 값을 변경할 수 있기 때문에 Immutable 클래스를 만들 수가 없다.

 

인자가 많은 생성자, 점진적 생성자 패턴, 그리고 자바 빈 패턴을 단점을 커버할 객체 생성 방법은 없을까? 그 대안이 바로 빌더 패턴(Builder Pattern)이다.

 

빌더 패턴

빌더 패턴은 GOF 디자인 패턴 중에 하나이다. 하지만 여기서 설명하는 빌더 패턴은 GOF에서 설명하는 빌더 패턴을 객체 생성 관점에서 변형에서 사용한 예라고 할 수 있다.

 

빌더 패턴은 객체를 생성할 클래스안에 빌더 클래스를 작성하고, 이 빌더 클래스를 이용하여 객체를 생성한다.

일반적인 빌더 패턴 만드는 순서는 다음과 같다.

 

1. 필수로 입력받아야하는 필드만을 가진 생성자를 작성한다.

2. 옵셔널로 입력받아야하는 필드는 setter로 만들고 추가적으로 자신을 리턴하도록 한다.

3. build() 메서드를 만들고 실제 객체 생성자에 자신을 넘겨 해당 객체를 리턴한다.

NutritionFacts cocaCola = new NutriFacts.Builder(240, 8)
      .calories(100).sodium(35).carbohydrate(30).build();
public class NutritionFacts {
	private final int servingSize;
	private final int servings;
	private final int calories;
	private final int fat;
	private final int sodium;
	private final int carbohydrate;

	private NutritionFacts(Builder builder) {
		servingSize = builder.servingSize;
		servings = builder.servings;
		calories = builder.calories;
		fat = builder.fat;
		sodium = builder.sodium;
		carbohydrate = builder.carbohydrate;
	}

	public static class Builder {
		private final int servingSize;
		private final int servings;

		private int calories = 0;
		private int fat = 0;
		private int sodium = 0;
		private int carbohydrate = 0;

		public Builder(int servingSize, int servings) {
			this.servingSize = servingSize;
			this.servings = servings;
		}

		public Builder calories(int val) {
			calories = val;
			return this;
		}

		public Builder fat(int val) {
			fat = val;
			return this;
		}

		public Builder sodium(int val) {
			sodium = val;
			return this;
		}

		public Builder carbohydrate(int val) {
			carbohydrate = val;
			return this;
		}

		public NutritionFacts build() {
			return new NutritionFacts(this);
		}
	}
}

 

빌더 패턴은 위에 다른 패턴의 단점을 해결하면서 여러 장점이 존재한다.

 

1. 빌더 패턴은 build() 메서드에서 한 번에 객체를 생성하기 때문에 필드의 유효성을 판단할 수 있다.

 

2. build()로 만들어진 객체 자체에 setter을 두지 않도록 하여 Immutable 하게 객체를 생성할 수 있다.

 

3. 빌더 객체는 재사용할 수 있다.

NutriFacts.Builder builder = new NutriFacts.Builder(240, 8)
      .calories(100).sodium(35).carbohydrate(30);
      
NutriFacts n1 = builder.build();

builder.sodium(0);
NutriFacts n2 = builder.build();

 

4. 빌더에 부가적인 기능을 추가 할 수 있다.

public class NutritionFacts {
    ...

	public static class Builder {
    	private static int count = 0;
        
        ...

		public NutritionFacts build() {
			count++;
			return new NutritionFacts(this);
		}
        
	}
}

 

단점으로는 코드의 양이 증가할 수 있으며, 다른 패턴보다는 성능이 약간 떨어진다.

 

하지만 요즘 여러 라이브러리에서 어노테이션만으로 빌더 패턴을 적용할 수 있도록 제공하고 있기 때문에 첫 번째 단점은 쉽게 해결할 수 있다. (lombok 등)

@Builder
public class Member {...}
반응형
반응형

객체를 만드는 방법인 생성자(Constructor) 대신 객체를 리턴하도록 하는 public으로 선언된 메서드인 정적 팩터리 메서드를 사용하는 것이 좋은 점이 많다.

 

정적 팩터리 메서드는 다양한 클래스에서 볼 수 있는데, String 클래스의 valueOf 메서드가 그중 하나이다.

    public static String valueOf(Object obj) {
        return (obj == null) ? "null" : obj.toString();
    }

    
    public static String valueOf(char data[]) {
        return new String(data);
    }

 

장점

1. 이름이 있다.

  • 생성자는 이름이 없어 오직 메소드 인자의 시그니처를 보고 파악해야 한다.
  • 정적 팩터리 메서드는 메서드 이름으로 어떤 의미의 객체를 리턴하는지 쉽게 파악할 수 있다.

 

2. 생성자처럼 반드시 새로운 객체를 만들도록 하지 않을 수 있다.

  • 내가 생각하는 가장 큰 장점 중에 하나로, 반드시 새로운 객체를 생성하지 않고, 이미 만들어져 있는 객체를 제공할 수도 있다.
  • 동일한 객체 생성을 요청하는 경우, 이미 만들어 놓은 객체 변수를 리턴할 수 도 있으며 또는 캐시를 사용하여 제공할 수 도 있다.
  • 객체 생성하는데 비용이 클 경우, Immutable 클래스인 경우 사용하기에 좋을 것이다.
    // Boolean code
        public static final Boolean TRUE = new Boolean(true);
        public static final Boolean FALSE = new Boolean(false);
    
        public static Boolean valueOf(boolean b){
            return b ? TRUE : FALSE;
        }

 

3. 생성자처럼 반드시 해당 클래스의 객체를 생성하지 않고, 하위 계층의 클래스를 생성할 수 있다.

  • 생성자는 반드시 자신의 객체만을 리턴한다.
  • 정적 팩터리 메서드는 반환형에 하위 계층 또는 구현체를 리턴할 수 있으므로 더욱 유연하다.
  • java.util.Collections는 다양한 정적 팩터리 메서드가 존재하는데 아래 예제에서는 리턴하는 객체가 Collections에서만 호출 가능한 inner class인 SynchronizedCollection를 생성하여 리턴한다. 이처럼 외부에서는 객체 생성 불가능한 클래스도 정적 메서드 클래스를 통해 리턴할 수 제공할 수 있다.
    package java.util;
    ...
    
    public class Collections {
        ...
    
        public static <T> Collection<T> synchronizedCollection(Collection<T> c) {
            return new SynchronizedCollection<>(c);
        }
    
    
        static class SynchronizedCollection<E> implements Collection<E>, Serializable {
            private static final long serialVersionUID = 3053995032091335093L;
    
            final Collection<E> c;  // Backing Collection
            final Object mutex;     // Object on which to synchronize
    
            SynchronizedCollection(Collection<E> c) {
                this.c = Objects.requireNonNull(c);
                mutex = this;
            }
            
            ...
        }
        
        
        ...
        
    }

 

4. 제네릭을 사용한 객체를 만들 때 편리하다. (JDK  1.7부터는 생성자에서도 지원한다.)

 

단점

1. 정적 팩터리 메서드만 제공하는 클래스라면, 즉 생성자가 private로 되어 있어 생성자를 이용하여 객체를 생성할 수 없다면, 하위 클래스를 만들 수 없다.

 

2. 정적 팩터리 메서드는 일반 메서드와 다른 점이 없으므로 해당 메서드가 정적 팩터리 메서드인지 바로 알 수는 없다. 따라서 문서를 읽어야지 파악 가능하다.

 

 

2번 단점으로 인해, 일반적으로 정적 팩터리 메서드의 네이밍은 아래와 같이 작성한다.

  • valueOf : 주어진 값과 같은 값을 갖는 객체를 반환한다.
        // String.valueOf
        public static String valueOf(Object obj) {
            return (obj == null) ? "null" : obj.toString();
        }​
  • of : valueOf와 동일
        // Optional.of
        public static <T> Optional<T> of(T value) {
            return new Optional<>(value);
        }​
  • getInstance : 일반적으로 이미 존재하는 객체를 반환한다.
    // Singleton example
    public class ExampleClass {
        private static ExampleClass instance = new ExampleClass();
        private ExampleClass() {}
        public static ExampleClass getInstance() {
            return instance;
        }
    }​
  • newInstance : 일반적으로 항상 새로운 객체를 만들어 반환한다.
    // Class.newInstance
    clazz.newInstance()​
  • getType : 특정 클래스에서 Type의 객체를 생성할 경우 사용한다. 일반적으로 이미 존재하는 객체를 반환한다.
  • newType : 특정 클래스에서 Type의 객체를 생성할 경우 사용한다. 일반적으로 항상 새로운 객체를 만들어 반환한다.

 

반응형
반응형

Hex2Bytes

1. BigInteger(String val, int radix)

	public static void main(String args[]) {
		String hex = "aaaaaa";
		byte[] bytes = new BigInteger(hex, 16).toByteArray();
	}

 

2. 순수 자바

	public static byte[] hexStringToByteArray(String s) {
		int len = s.length();
		byte[] data = new byte[len / 2];
		for (int i = 0; i < len; i += 2) {
			data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
			        + Character.digit(s.charAt(i + 1), 16));
		}
		return data;
	}

 

3. javax.xml.bind.DatatypeConverter.parseHexBinary(String lexicalXSDHexBinary)

 

4. org.apache.commons.codec.binary.Hex.decodeHex(char[] data)

성능 비교

BigInteger는 10^7부터는 너무 오래걸려서 테스트 불가능했다.

 

 

차트에는 BigInteger의 10^6데이터는 표현하지 않았다.

 

 

- BigInterger가 성능이 제일 좋지 않은것으로 판단된다. BigInteger라도 너무 큰 숫자를 처리하기에는 성능 이슈가 있는것으로 판단된다.

 

- DatatypeConverter.parseHexBinary는 작은 데이터도 평균적으로 0.15초는 적어도 소요되었다. 신기해서 소스를 보니, 아래 소스처럼 처음 수행될 때 실제 컨버터 구현체를 인스턴스화 한다. 만약 실시간성이 중요하다면, 미리 인스턴스화 하는게 좋을 것 같다. 

    public static java.math.BigInteger parseInteger( String lexicalXSDInteger ) {
        if (theConverter == null) initConverter();
        return theConverter.parseInteger( lexicalXSDInteger );
    }
    
    private static synchronized void initConverter() {
        theConverter = new DatatypeConverterImpl();
    }

 

- DatatypeConverter 초기화 시간만 제외한다면 BigInteger를 제외한 세 개는 거의 동일한 성능을 보인다.

 

- DatatypeConverter 또는 Hex를 기본적으로 어플리케이션에서 사용하지 않는다면, 순수자바로 구현된 hexStringToByteArray를 사용하는 것이 좋다고 생각된다.

반응형
반응형

자바의 가상 머신은 두 가지 종류의 이벤트를 받아 프로그램을 종료한다.

  1. 프로그램이 정상적으로 종료되거나 System.exit()
  2. 사용자 인터럽트(Ctrl+C)나 사용자 로그오프 또는 시스템 종료와 같은 시스템 전체 이벤트에 대한 응답 이벤트

 

프로그램이 정상 또는 비정상 종료하기 전에 특정 작업을 수행할 수 있도록 자바는 java.lang.Runtime.addShutdownHook(Thread t)을 제공한다. 종료될 때 수행할 내용을 정의한 Thread를 인자로 전달한다.

 

기본 예제

아래 결과는 “End”가 출력된 후, “Hook Run” 이 출력된다.

public class ShutdownHookTest {

	static class HookThread extends Thread {
		@Override
		public void run() {
			System.out.println("Hook Run");
		}
	}

	public static void main(String[] args) {
		Runtime.getRuntime().addShutdownHook(new HookThread());

		System.out.println("End");
	}
}

 

아래처럼 비정상 동작으로 프로그램이 종료되어 “End”는 출력되지 않지만 “Hook Run”이 출력된다.

public class ShutdownHookTest {

	static class HookThread extends Thread {
		@Override
		public void run() {
			System.out.println("Hook Run");
		}
	}

	public static void main(String[] args) {
		Runtime.getRuntime().addShutdownHook(new HookThread());

		int errorNum = 1 / 0;
		System.out.println("End");
	}
}

 

콘솔에서 sleep 동안 인터럽트(Ctrl+C)를 줄 경우에도 “End”는 출력되지 않지만 “Hook Run”이 출력된다.

public class ShutdownHookTest {

	static class HookThread extends Thread {
		@Override
		public void run() {
			System.out.println("Hook Run");
		}
	}

	public static void main(String[] args) {
		Runtime.getRuntime().addShutdownHook(new HookThread());

		try {
			System.out.println("sleep 3s");
			Thread.sleep(3000);
		} catch (Exception e) {
			e.printStackTrace();
		}
        
		System.out.println("End");
	}
}

 

순서 지정 불가능

Shutdown Hook은 실행되는 순서를 결정할 수 없다. 즉 addShutdownHook() 메소드끼리의 호출 순서에 상관없이 동시에 실행된다. 아래처럼 HookThread를 먼저 등록하고, HookThread2를 등록하였지만 실행할 때마다 출력되는 순서는 랜덤 하다.

public class ShutdownHookTest {

	static class HookThread extends Thread {
		@Override
		public void run() {
			System.out.println("Hook Run1");
		}
	}

	static class HookThread2 extends Thread {
		@Override
		public void run() {
			System.out.println("Hook Run2");
		}
	}

	public static void main(String[] args) {
		Runtime.getRuntime().addShutdownHook(new HookThread());
		Runtime.getRuntime().addShutdownHook(new HookThread2());

		System.out.println("End");
	}
}

 

Hook의 실행 막기

Runtime.halt(int)를 사용하면 등록된 Shutdown Hook을 실행하지 않고 종료된다. 만약 종료 중에 Halt가 호출될 경우 (종료 중이라면 등록된 Shutdown Hook 쓰레드들이 동시에 실행되는 상태) 실행 중인 Hook이 마칠 때까지 대기하지 않고, 바로 종료한다. 아래 예제를 보면 종료되기 전에 halt() 메소드를 호출하였으므로 등록된 Hook이 실행되지 않는다.

	// ...
	
	public static void main(String[] args) {
		Runtime.getRuntime().addShutdownHook(new HookThread());
		Runtime.getRuntime().addShutdownHook(new HookThread2());

		System.out.println("End");
		Runtime.getRuntime().halt(0);
	}
}

 

Hook 발생 중 Hook 제어 불가능

Shutdown Hook이 시작되면 Hook을 추가할 수도 제거할 수 없다. 시도할 경우 IllegalStateException 예외가 발생한다. 아래 예제는 예외가 발생하는 경우이다. Shutdown Hook에 추가한 Hook Thread에 새로운 Hook을 추가하였다. 따라서 종료되면서 IllegalStateException 예외가 발생한다.

public class ShutdownHookTest {

	static class HookThread extends Thread {
		@Override
		public void run() {
			Runtime.getRuntime().addShutdownHook(new HookThread());
			System.out.println("Hook Run1");
		}
	}
	
	// ...

}

 

이외 유의사항

쓰레드는 언제나 어렵다. 복잡하고 디버깅하기도 힘들다. Shutdown Hook도 마찬가지로 쓰레드를 사용하므로 제대로 구현하지 않는다면 원하는 대로 작동하지 않을 수 있다.

 

프로그램이 종료될 때 모든 쓰레드 Hook이 동시에 실행되며 이외에 쓰레드들도 실행 중일 것이다. 따라서 thread-safe 하게 작성하여 데드락을 피해야 한다.

 

뿐만 아니라, 무조건 실행된다는 보장이 없으므로 Shutdown Hook 메소드를 100% 신뢰하지는 않는 것이 좋다. 정말 중요한 작업이라면 명시적인 종료자 메소드를 구현하여 사용하는 게 안전할 것이다.

 

마지막으로 Hook 쓰레드에서 많은 시간을 소모하면 안 된다. 만약 시스템을 종료하는 상황이라면 시스템이 종료되면 JVM이 종료되면서 Hook이 실행되겠지만 시간이 오래 걸릴 경우, 시스템은 무시하고 강제로 종료돼버린다.

반응형
반응형

멀티쓰레드 프로그래밍에서 쓰레드들이 모든 작업을 마친 후에 특정한 작업을 해야하는 경우가 있다. 이를 위해 다른 쓰레드들에서 일련의 작업이 완료 될 때까지 대기하도록 Sync를 맞춰주는 기능을 자바는 java.util.concurrent.CountDownLatch(int count)을 제공한다.

 

CountDownLatch를 인스턴스화할 때 주어진 카운트로 초기화된다. await 메서드를 실행하면 해당 쓰레드는 다른 쓰레드에서 countDown 메서드 호출로 인해 현재 카운트가 0이 될 때까지 대기한다. 그 후에는 대기중인 스레드가 해제된다.

 

  1. Main 쓰레드는 latch.await() 메소드에 의해 대기상태로 진입한다.
  2. 이 후 Worker 쓰레드들이 자신의 작업을 마친 후, latch.countDown() 메소드를 통해 카운트를 줄여간다.
  3. 카운트가 0이 되면 Main 쓰레드를 대기상태에서 해제된다.
public class CountDownLatchExample {
	static final int max = 3;

	public static void testCountDownLatch() throws Exception {
		final CountDownLatch latch = new CountDownLatch(max);

		for (long i = 1; i <= max; i++) {
			new Thread(new Worker(latch, i * 100)).start();
		}

		latch.await();

		System.out.println("########### CountDownLatch End ###########");
	}

	static class Worker implements Runnable {
		private CountDownLatch latch;
		private long n;

		public Worker(CountDownLatch latch, long n) {
			this.latch = latch;
			this.n = n;
		}

		@Override
		public void run() {
			try {
				int cnt = 0;
				for (int i = 0; i < n; i++) {
					cnt++;
				}

				System.out.println(cnt);
			} catch (Exception ex) {
				ex.printStackTrace();
			} finally {
				if (this.latch == null) {
					return;
				}

				latch.countDown();
			}
		}
	}

	public static void main(String[] args) throws Exception {
		System.out.println("start");
		testCountDownLatch();
		System.out.println("end");
	}

	/**
	 * start
	 * 200
	 * 100
	 * 300
	 * ########### CountDownLatch End ###########
	 * end
	 */
}
반응형

+ Recent posts