반응형

이전 블로그에서 2017년 7월 21일에 작성한 글


Constant Interface를 위키에서 보다가 Constant Interface는 런타임 시에는 사용할 목적이 없지만, Marker Interface는 런타임 시에 사용할 목적이 있다고 표현되어 있었다. 고로 Marker Interface도 인터페이스이지만 메서드 선언이 없는 인터페이스라고는 추측은 되지만 그렇다면 어떤 용도로 사용하는지 자료를 찾아보았다.

 

Marker Interface

Marker Interface란 아무것도, 즉 변수와 메서드를 정의하지 않은 인터페이스이다. 먼저 대표적인 Marker Interface에는 ​자바에서 종종 보는 Serializable, Cloneable​ 인터페이스가 있다. 대학원을 다녔을 때, 오브젝트 정보를 파일로 저장하고 불러올 때 사용을 해봤지만 안에 어떤 것이 들어있는지 확인은 하지 않았다. 아래는 ​Cloneable​의 코드이다.

package java.lang;

/**
 * A class implements the <code>Cloneable</code> interface to
 * indicate to the {@link java.lang.Object#clone()} method that it
 * is legal for that method to make a
 * field-for-field copy of instances of that class.
 * <p>
 * Invoking Object's clone method on an instance that does not implement the
 * <code>Cloneable</code> interface results in the exception
 * <code>CloneNotSupportedException</code> being thrown.
 * <p>
 * By convention, classes that implement this interface should override
 * <tt>Object.clone</tt> (which is protected) with a public method.
 * See {@link java.lang.Object#clone()} for details on overriding this
 * method.
 * <p>
 * Note that this interface does <i>not</i> contain the <tt>clone</tt> method.
 * Therefore, it is not possible to clone an object merely by virtue of the
 * fact that it implements this interface.  Even if the clone method is invoked
 * reflectively, there is no guarantee that it will succeed.
 *
 * @author  unascribed
 * @see     java.lang.CloneNotSupportedException
 * @see     java.lang.Object#clone()
 * @since   JDK1.0
 */
public interface Cloneable {
}

 

그렇다면 Marker Interface는 어디에 사용될까? instanceof 연산자를 사용하여 런타임에서 객체에 대한 타입을 확인하여 프로그램의 흐름을 제어할 수 있다. 즉 Maker Interface는 특별한 기능을 하는 것이 아닌 단순히 해당 객체의 타입을 구분하는 정도로 사용한다.

 

예를 들어 오브젝트를 입력받아 스트림에 쓰는 ObjectOutputStream.writeObject(Object obj)는 Object를 인자로 받는다. 이 매소드는 writeObject0(Object obj, ...)​을 호출한다. 이 매소드는 Object의 타입에 따라 다시 알맞은 매소드를 호출한다. 아래는 writeObject0(Object obj, ...)​의 코드 일부이다. (String, Enum도 Serializable이지만 처리를 다르게 하려고 구분한 것으로 보임)

public class ObjectOutputStream extends OutputStream implements ObjectOutput, ObjectStreamConstants {

 // ...
 private void writeObject0(Object obj, boolean unshared) throws IOException {

  // ...
  if (obj instanceof String) {
   writeString((String) obj, unshared);
  } else if (cl.isArray()) {
   writeArray(obj, desc, unshared);
  } else if (obj instanceof Enum) {
   writeEnum((Enum<?>) obj, desc, unshared);
  } else if (obj instanceof Serializable) {
   writeOrdinaryObject(obj, desc, unshared);
  } else {
   if (extendedDebugInfo) {
    throw new NotSerializableException(cl.getName() + "\n" + debugInfoStack.toString());
   } else {
    throw new NotSerializableException(cl.getName());
   }
  }

  // ...
 }
}
반응형
반응형

이전 블로그에서 2017년 7월 20일에 작성한 글


 

프로젝트 소스를 보다가 상수(Constant)를 인터페이스(Interface)에 정의한 것을 발견했다. 학교 수업과 책으로 자바를 배웠을 때, 항상 상수는 클래스에 정의한 것만 봐왔던 나는 생소했다. 당연히 초보 개발자로서, 특별한 장점이 있으므로 실무에서는 인터페이스를 사용하지 않을까 생각해 자료를 찾아보았다. 결론부터 말하면 사용을 추천하지 않는 Anti 패턴이다.

 

Constant Interface

Constant Interface란 오직 상수만 정의한 인터페이스이다. 인터페이스의 경우, ​변수를 등록할 때 자동으로 public static final​이 붙는다. 따라서 상수처럼 어디에서나 접근할 수 있다. 그 뿐만 아니라 하나의 클래스에 여러 개의 인터페이스를 Implement 할 수 있는데, Constant Interface를 Implement 할 경우, ​인터페이스의 클래스 명을 네임스페이스로 붙이지 않고 바로 사용할 수 있다. 이러한 편리성 때문에 Constant Interface를 사용한다. 아래는 간단한 예이다. 

public interface Constants {
 double PI = 3.14159;
 double PLANCK_CONSTANT = 6.62606896e-34;
}

public class Calculations implements Constants {

 public double getReducedPlanckConstant() {
  return PLANCK_CONSTANT / (2 * PI);
 }
}

 

Constant Interface 문제점

Constant Interface를 써도 컴파일이 안 되는 것도 아니고 그렇게 잘못된 것 같지는 않아 보인다. 하지만 위키 Effective Java (규칙19) 책을 보면 다음과 같은 이유로 Anti 패턴으로 간주한다.

 

1. Implement 할 경우 사용하지 않을 수도 있는 상수를 포함하여 모두 가져오기 때문에 계속 가지고 있어야 한다.

 

2. 컴파일할 때 사용되겠지만, 런타임에는 사용할 용도가 없다. (Marker Interface는 런타임에 사용할 목적이 있으므로 다름)

 

3. Binary Code Compatibility (이진 호환성)을 필요로 하는 프로그램일 경우, 새로운 라이브러리를 연결하더라도, 상수 인터페이스는 프로그램이 종료되기 전까지 이진 호환성을 보장하기 위해 계속 유지되어야 한다.

 

4. IDE가 없으면, 상수 인터페이스를 Implement 한 클래스에서는 상수를 사용할 때 네임스페이스를 사용하지 않으므로, 해당 상수의 출처를 쉽게 알 수 없다. 또한 상수 인터페이스를 구현한 클래스의 하위 클래스들의 네임스페이스도 인터페이스의 상수들로 오염된다.

 

5.  인터페이스를 구현해 클래스를 만든다는 것은, 해당 클래스의 객체로 어떤 일을 할 수 있는지 클라이언트에게 알리는 행위이다. 따라서 상수 인터페이스를 구현한다는 사실은 클라이언트에게는 중요한 정보가 아니다. 다만, 클라이언트들을 혼동시킬 뿐이다.

 

6. 상수 인터페이스를 Implement 한 클래스에 같은 상수를 가질 경우, 클래스에 정의한 상수가 사용되므로 사용자가 의도한 흐름으로 프로그램이 돌아가지 않을 수 있다. 아래는 간단한 예제이다.

public interface Constants {
 public static final int CONSTANT = 1;
}

public class Class1 implements Constants {

 public static final int CONSTANT = 2; // *

 public static void main(String args[]) throws Exception {
  System.out.println(CONSTANT);
 }
}

 

Constant Interface 대안

자바문서에서 Constant Interface를 Anti 패턴으로 명시하였고 이 방안으로 ​import static 구문​ 사용을 권장한다.​ Constant Interface와 동일한 기능과 편리성을 제공한다. 아래는 간단한 예제이다.

public final class Constants {
 private Constants() {
  // restrict instantiation
 }

 public static final double PI = 3.14159;
 public static final double PLANCK_CONSTANT = 6.62606896e-34;
}



import static Constants.PLANCK_CONSTANT;
import static Constants.PI;

public class Calculations {

 public double getReducedPlanckConstant() {
  return PLANCK_CONSTANT / (2 * PI);
 }
}

 

반응형
반응형

이슈

POI는 아파치에서 만든 라이브러리로서 마이크로소프트 오피스 파일 포맷을 순수 자바 언어로서 읽고 쓰는 기능을 제공한다.

 

회사 제품에 데이터를 엑셀로 다운받을 수 있는 기능이 있는데 톰캣이나 Jetty was를 사용하였을 때는 아무런 에러가 발생하지 않았지만, weblogic을 사용하는 사이트에서 아래와 같은 에러가 발생하였다.

 

java.lang.NoClassDefFoundError: Could not initialize class org.apache.poi.POIXMLTypeLoader

 

 

원인

제품에서 사용하는 POI 라이브러리와 Weblogic에서 자체적으로 지원하는 POI 라이브러리와 충돌해서 발생하는 에러이다.

 

 

해결

WEB-INF 아래에 weblogic.xml 파일 생성 후 아래와 같은 옵션 추가하여 어플리케이션 내에 라이브러리를 우선적으로 사용하도록 설정한다.

<container-descriptor>
	<prefer-web-inf-classes>true</prefer-web-inf-classes>
</container-descriptor>
반응형
반응형

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