자바 단일 사례 의 몇 가지 쓰기 분석 (1)

단일 모드 안내
단일 모델 은 소프트웨어 디자인 모델 에서 가장 간단 한 디자인 모델 이다.이름 에서 알 수 있 듯 이 단일 사례 의 목적 은 시스템 에 이러한 종류의 유일한 인 스 턴 스 만 포함 시 키 는 것 이다.
단일 모델 의 최초 정 의 는 (에디슨 비 슬 리, 1994) 에 나 타 났 다. "하나의 인 스 턴 스 만 있 고 전체 방문 점 을 제공 합 니 다."
자바 의 단일 모드 정의: "하나의 클래스 가 있 고 하나의 인 스 턴 스 만 있 으 며 자체 적 으로 전체 시스템 에 제공 합 니 다."
시스템 의 일부 유형 에 있어 하나의 인 스 턴 스 만 중요 합 니 다. 예 를 들 어 한 시스템 에 여러 개의 인쇄 작업 이 존재 할 수 있 지만 작업 중인 작업 만 있 을 수 있 습 니 다.하나의 시스템 에 창 관리자 나 파일 시스템 만 있 을 수 있 습 니 다.하나의 시스템 에 타이머 나 ID (번호) 생 성기 만 있 을 수 있 습 니 다.Windows 에서 작업 관리자 하나만 열 수 있 습 니 다.시스템 을 사용 하지 않 고 창 대상 을 유일 화 하지 않 으 면 여러 창 이 팝 업 됩 니 다. 이 창 들 이 표시 하 는 내용 이 완전히 일치 하면 중복 대상 으로 메모리 자원 을 낭비 합 니 다.이 창 들 이 표시 하 는 내용 이 일치 하지 않 으 면 어느 순간 시스템 에 여러 상태 가 있 고 실제 와 맞지 않 으 며 사용자 에 게 오 해 를 줄 수 있 으 며 어느 것 이 실제 상태 인지 모른다 는 뜻 이다.따라서 시스템 의 한 대상 의 유일 성 을 확보 하 는 것 이 하나의 사례 만 있 는 것 이 중요 하 다.
본 고 는 자바 언어 로 단일 사례 류 의 몇 가지 실현 방식 을 묘사 하고 제 시 된 몇 가지 실현 방식 의 장단 점 을 분석 하 며 프로그램 으로 성능 데 이 터 를 검증 할 것 이다.
 
 
자바 실례 화 방식
      자바 언어 에서 인 스 턴 스 를 만 드 는 방법 은 크게 4 가지 가 있다 는 것 을 알 고 있 습 니 다.
   1 、 new 키워드 사용
 
import java.io.Serializable;
import java.util.Random;

/**
 * @author thinkpad
 *
 */
public class Singleton implements Serializable{
	
	private static final long serialVersionUID = -3868390997950184335L;
	
	private static Random random = new Random();
	
	private int id;
	
	private String name;
	
	private int [] data = {
			random.nextInt(),
			random.nextInt(),
			random.nextInt()
	};
	//default constructor  
	public Singleton(){
		
	}
	//public constructor
	public Singleton(int id){
		this.id = id * 2;
	}
	//private constructor
	private Singleton(int id,String name){
		this(id);
		this.name = name + "." + name;
	}
	
	@Override
	public String toString(){
		StringBuffer buf = new StringBuffer();
		buf.append(super.toString()).append("
") .append("id :").append(id).append("
") .append("name :").append(this.name).append("
") .append("data array :").append(data.toString()); return buf.toString(); } }

  가장 흔히 볼 수 있 는 new 키 워드 를 사용 하여 대상 을 만 듭 니 다.
public class TestSingleton {

	public static void main(String[] args) {
		Singleton single = new Singleton(1);
	}
}

 
   2. 반사 사용
     반 사 를 사용 하면 JAVA 에서 볼 수 있 는 권한 을 돌파 할 수 있 습 니 다. 이 예제 에 서 는 싱글 톤 의 개인 적 인 구조 방법 을 사용 하 였 습 니 다.
   
import java.lang.reflect.Constructor;
import java.lang.reflect.Type;

/**
 * @author thinkpad
 *
 */
public class TestSingletonUseReflect {

	public static void main(String[] args) {
		try {
			Constructor<?>[] constructors = Singleton.class.getDeclaredConstructors();
			System.out.println(constructors.length);
			for(Constructor<?> con : constructors ) {
				Type[] types = con.getParameterTypes();
				if (types.length == 2){
					con.setAccessible(true);
					Singleton singleton = null;
					singleton = (Singleton) con.newInstance(1,"single1");
					System.out.println(singleton);
					
					Singleton singleton2 = null;
					singleton2 = (Singleton) con.newInstance(2,"single2");
					System.out.println(singleton2);
				}
			}
		}catch(Exception e){
			e.printStackTrace();
		}
	}
}
 
 
   3. 역 직렬 화 사용
    자바 대상 직렬 화 (Object Serialization) 는 Serializable 인 터 페 이 스 를 실현 한 대상 을 하나의 바이트 시퀀스 로 바 꾸 고 반 직렬 화 를 통 해 이 바이트 시퀀스 를 원래 의 대상 으로 완전히 복원 할 수 있다 는 것 을 알 고 있 기 때문에 반 직렬 화 는 대상 을 만 드 는 방식 을 제공 합 니 다.반 직렬 화 생 성 대상 을 사용 하 는 것 과 new 키 워드 를 사용 하 는 생 성 대상 이 다 르 기 때문에 반 직렬 화 과정 에서 구조 방법 은 호출 되 지 않 았 습 니 다 (꼭 그렇지 는 않 습 니 다. 직렬 화 대상 부모 류 가 Serializable 인 터 페 이 스 를 실현 하지 않 으 면 Object 류, 직렬 화 과정 에서 부모 류 의 무 참 구조 함 수 를 재 귀적 으로 호출 합 니 다).도 메 인 초기 화 코드 도 실행 되 지 않 았 습 니 다.
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

/**
 * @author thinkpad
 *
 */
public class TestSerializable {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		Singleton single1 = new Singleton(100);
		Singleton single2 = new Singleton(200);
		
		try { 
			ByteArrayOutputStream buf = new ByteArrayOutputStream();
			ObjectOutputStream out = new ObjectOutputStream(buf);
			System.out.println("**********begin serializable**********");
			System.out.println(single1);
			System.out.println(single2);
			out.writeObject(single1);
			out.writeObject(single2);
			out.close();
			System.out.println("**********begin unserializable**********");
			ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(buf.toByteArray()));
			Singleton s1 = (Singleton)in.readObject();
			Singleton s2 = (Singleton)in.readObject();
			System.out.println(s1.toString());
			System.out.println(s2.toString());
			in.close();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} 
	}
}

 
   4. 주입 의존 (Spring DI)
    의존 주입 은 JVM 의 측면 에서 볼 때 대상 을 만 드 는 방식 이 아 닐 것 이다.주입 의존 개념 에 대해 서 는 Spring 공식 사 이 트 를 참고 하 시기 바 랍 니 다.
 
자바 단일 사례 의 몇 가지 실현 방식
       단일 모델 의 정의 와 설명 에 따라 단일 모델 을 실현 하려 면 몇 가지 문 제 를 고려 해 야 한다.단일 사례 를 어떻게 만 들 고 누가 만 드 는 지, 인 스 턴 스 의 유일 성 을 어떻게 확보 하 는 지, 전체적인 방문 점 을 어떻게 제공 하 는 지.자바 언어의 실례 화 는 여러 가지 가 있 기 때문에 자바 의 단일 사례 는 각종 조건 에서 인 스 턴 스 의 유일 성 을 유지 하고 라인 안전, 로드 지연, 반사 공격, 반 서열 화 공격 등에 잘 저항 할 수 있어 야 한다.본 논문 의 다음 내용 은 흔히 볼 수 있 는 몇 가지 사례 의 디자인 방법 을 분석 하고 검증 하 며 코드 로 효 과 를 검증 할 것 이다 (인터넷 이론 은 많이 말 하지만 대부분 실제 코드 검증 이 부족 하 다).
       단일 사례 를 실현 하려 면 반드시 개인 적 인 구조 기 이 고 공유 하 는 정적 인 스 턴 스 를 도 출 해 야 한다. 정적 인 스 턴 스 의 초기 화 방식 에 따라 굶 주 린 사람 형 과 게 으 른 사람 형 으로 나 눌 수 있 고 사례 의 노출 방식 에 따라 직접 노출 과 공장 방법 에 따라 간접 적 으로 노출 할 수 있다.굶 주 린 사람 형 은 클래스 로 딩 할 때 실례 화 를 완성 하고 게 으 른 사람 형 은 실제 사용 할 때 만 실례 화 를 한다.따라서 굶 주 린 사람의 한 예 는 스 레 드 안전 을 확보 할 수 있 는 (JVM 으로 ClassLoader 를 한 번 만 불 러 오고 여러 개의 ClassLoader 의 상황 을 고려 하지 않 음) 이 고 게 으 른 사람의 스 레 드 안전 은 추가 적 인 동기 화 를 통 해 제어 해 야 한다.굶 주 린 사람 형 은 게으름뱅이 식 지연 로드 를 실현 할 수 없다.
   
1. 직접 노출 - 굶 주 린 사람 형 (1)
      이런 글 씨 는 매우 간단 하고 거 칠 며 수 요 를 잘 만족 시 킬 수 있 습 니 다. 하하, 가끔 은 간단 하고 거 칠 어도 문 제 를 가장 빨리 해결 할 수 있 습 니 다.이런 방식 은 라인 이 안전 하지만 유연성 이 부족 해서 확장 이 쉽 지 않다.
 
 
import java.io.Serializable;

public class DirectHungrySingleton implements Serializable{
	
	private static final long serialVersionUID = 6069877357741265707L;

	//    
	public static final DirectHungrySingleton singleton = new DirectHungrySingleton();
	
	//       
	private DirectHungrySingleton(){
	}
}
    2. 공장 방법 - 굶 주 린 사람 형
          공장 방법 으로 사례 를 노출 시 키 고 대외 적 으로 통 일 된 API 인 터 페 이 스 를 제공 하여 확장 성과 유연성 이 강하 고 스 레 드 안전성 도 있 으 나 로드 지연 이 필요 한 장소 에 적용 되 지 않 습 니 다.
 
 
import java.io.Serializable;

public class FactoryMethodHungrySingleton implements Serializable{
	
	private static final long serialVersionUID = 6069877357741265707L;

	//       
	private static final FactoryMethodHungrySingleton singleton = new FactoryMethodHungrySingleton();
	
	//       
	private FactoryMethodHungrySingleton(){
	}
	//      
	public static FactoryMethodHungrySingleton getInstance(){
		return singleton;
	}
}
   
3. 공장 방법 - 게으름뱅이 형 (1) 동기 화 사용
     지연 초기 화 방식 으로 JVM 을 통 해 인 스 턴 스 의 유일 성 을 확보 할 수 없 기 때문에 공장 방법 에 synchronized 키 워드 를 추가 하여 스 레 드 안전 을 확보 합 니 다.이런 표기 법 은 라인 이 안전 하고 로 딩 이 지연 되 는 장점 을 가지 고 있 으 나 코드 성능 상의 손실 을 가 져 왔 다.이 대상 이 실례 화 되 었 든 없 든 getInstance () 방법 은 반드시 Class 대상 자 물 쇠 를 얻어 야 하 며, 자 물 쇠 를 추가 하고 자 물 쇠 를 풀 면 일정한 성능 이 손실 될 수 밖 에 없다.
 
 
 
import java.io.Serializable;

public class FactoryMethodLazySingleton implements Serializable{
	
	private static final long serialVersionUID = 6069877357741265707L;

	//       
	private static FactoryMethodLazySingleton singleton = null;
	
	//       
	private FactoryMethodLazySingleton(){
	}
	//4.        【        ,      ,          (new             ),      getSingleton  ,        】
	public static synchronized FactoryMethodLazySingleton getInstance(){
		if (singleton == null){
			singleton = new FactoryMethodLazySingleton();
		}
		return singleton;
	}
}
    
 
4. 공장 방법 - 게으름뱅이 형 (2) 이중 검사 사용 (jdk 1.5 + 적용)
    우 리 는 코드 를 분석 한 결과, 실례 화 과정 에서 여러 라인 이 들 어가 지 못 하도록 제어 하면 된다 는 것 을 발견 했다. 잠 긴 입도 최소 화 원칙 에 따라 뉴 코드 줄 에 자 물 쇠 를 추가 하면 된다.이것 은 유명한 이중 검사 쓰기 입 니 다. 일부 사이트 자료 에 jdk 1.5 + 에 만 적 용 됩 니 다. 제 개인 적 인 이 해 는 jdk 1.5 + 이후 volatile 키워드 기능 의 강화 로 다 중 스 레 드 환경 에서 변수 읽 기와 쓰기 의 가시 성 을 확보 할 수 있 습 니 다.하지만 구체 적 인 자 료 는 아직 찾 지 못 했 습 니 다. 아 시 는 분 께 알려 주세요.
    
import java.io.Serializable;

public class FactoryMethodLazyDCSingleton implements Serializable{
	
	private static final long serialVersionUID = 6069877357741265707L;

	//       
	//  volatile            
	private static volatile FactoryMethodLazyDCSingleton singleton = null;
	
	//       
	private FactoryMethodLazyDCSingleton(){
	}
	//      
	public static FactoryMethodLazyDCSingleton getInstance(){
		//    ,             ,    
		if (singleton == null){
			synchronized(FactoryMethodLazyDCSingleton.class){
				//    ,         ,     
				if (singleton == null)
					singleton = new FactoryMethodLazyDCSingleton();
			}
		}
		return singleton;
	}
}

 5. 공장 방법 - 게으름뱅이 형 (3) 내부 정적 클래스 사용 하기
    이중 검 사 를 사용 하면 스 레 드 안전 과 로드 지연 을 잘 만족 시 킬 수 있 지만 코드 작성 이 복잡 하고 오류 가 발생 하기 쉬 우 며 인터넷 에서 내부 정적 류 를 사용 하 는 쓰기 가 나타 나 매우 우아 하 다.이 는 내부 클래스 의 지연 로드 특성 을 통 해 lazy loading 을 실현 하 는 동시에 JVM 의 클래스 로드 체 제 를 통 해 스 레 드 안전 을 확보 하 는 매우 우아 한 쓰기 입 니 다.
   
import java.io.Serializable;

public class FactoryMethodLazyInnerClassSingleton implements Serializable {

	private static final long serialVersionUID = 6069877357741265707L;
	
	//     
	private static class Holder {
		static final FactoryMethodLazyInnerClassSingleton singleton 
			= new FactoryMethodLazyInnerClassSingleton();
	}
	
	//        
	private FactoryMethodLazyInnerClassSingleton() {
	}

	//       
	public static FactoryMethodLazyInnerClassSingleton getInstance() {
		return Holder.singleton;
	}
}

  
  6 、 Enum (Jdk 1.5 +) 사용
     jdk 1.5 에서 Enum 키 워드 를 제공 하여 매 거 진 유형 을 정의 합 니 다. 우 리 는 매 거 진 유형 이 특수 한 유형 이라는 것 을 알 고 있 습 니 다.이 는 다음 과 같은 특징 이 있 습 니 다. 유형 안전 을 확보 하기 위해 대외 적 으로 공유 하 는 구조 방법 을 제공 하지 않 습 니 다. 모든 매 거 진 대상 은 클래스 로 딩 할 때 초기 화 를 완 료 했 고 모두 static final 유형 입 니 다. 이런 특징 은 첫 번 째 표기 법, 간단 하고 우아 하 며 스 레 드 안전 과 완전히 유사 하 며 반 직렬 화 공격 에 저항 하 는 체 제 를 제공 합 니 다.
 
//jdk1.6 Enum     199        enum      ,      
/**
      * prevent default deserialization
      */
    private void readObject(ObjectInputStream in) throws IOException,
        ClassNotFoundException {
            throw new InvalidObjectException("can't deserialize enum");
    }

  
 
public enum EnumSingleton {
	
	//    
	INSTANCE(1, "other");
	
	//     
	private int intField;

	private String otherField;

	private EnumSingleton(int intField, String otherField) {
		this.intField = intField;
		this.otherField = otherField;
		System.out.println("init the enum type");
	}

	@Override
	public String toString() {
		return new StringBuffer().append(super.toString()).append(intField)
				.append(otherField).toString();
	}
}

좋은 웹페이지 즐겨찾기