디자인 모델 - 단일 모델 [실현, 직렬 화, 반사]

11526 단어
디자인 모델 - 단일 모델 [실현, 직렬 화, 반사]
[toc]
1. 실현
단일 모델 의 실현 은 여러 가지 가 있 고 분류 방식 도 다양 하 다. 예 를 들 어 예비 로드 와 게 으 른 로드, 그리고 스 레 드 안전 의 실현 과 스 레 드 안전 하지 않 은 실현 으로 나 뉜 다.
1.1. 스 레 드 가 안전 하지 않 음
1.1.1 굶 주 림 식
호출 시 인 스 턴 스 가 초기 화 되 었 는 지, 없 으 면 초기 화 되 고 할당 되 었 는 지 판단 합 니 다.장점:
  • 게 으 른 로드
  • 운행 효율 이 높다
  • 단점:
  • 비 스 레 드 보안: 인 스 턴 스 가 초기 화 되 지 않 았 을 때 여러 스 레 드 가 동시에 getInstance 방법 을 호출 하면 각 스 레 드 가 서로 다른 인 스 턴 스
  • 를 얻 을 수 있 습 니 다.
    적용: 확실 하지 않 으 면 다 중 스 레 드 로 호출 되 지 않 습 니 다. 그렇지 않 으 면 사용 을 권장 하지 않 습 니 다.
    public static PlainNotSafe getInstance() {
            if (instance == null) {
                instance = new PlainNotSafe();
            }
            return instance;
        }
    

    1.2. 스 레 드 안전
    1.2.1 배 부 른 한식
    클래스 초기 화 과정 에서 인 스 턴 스 생 성:
    장점:
  • 단순 실현
  • 스 레 드 안전: 인 스 턴 스 초기 화 는 클래스 로드 단계 에서 이 루어 지고 JVM 내부 에서 이 과정의 스 레 드 안전성 을 확보 합 니 다
  • 단점:
  • 비 게 으 름 로드
  • 적용: 인 스 턴 스 초기 화 에 소모 되 는 자원 이 적 거나 시작 시간 이 민감 하지 않 거나 업무 요구 가 시 작 된 후 빠 른 응답
    class LoadAhead implements Singleton{
        private static LoadAhead instance = new LoadAhead();
        private LoadAhead(){}
        public static LoadAhead getInstance(){
            return instance;
        }
    }
    

    1.2.2 단일 동기 잠 금
    syncronized 키 워드 를 사용 하여 getInstance 방법 을 수식 합 니 다.
        public static synchronized LazyLoadWithOneSynchronization getInstance(){
            if (instance == null) {
                instance = new LazyLoadWithOneSynchronization();
            }
            return instance;
        }
    

    장점:
  • 스 레 드 안전
  • 단순 실현
  • 단점:
  • 높 은 병발 환경, 실행 효율 이 낮 음: 동시에 하나의 스 레 드 만 인 스 턴 스
  • 를 얻 을 수 있 습 니 다.
    적용: 어떤 필드 에 도 적용 되 지 않 음
    1.2.3 이중 검사 + 동기 잠 금
    인 스 턴 스 생 성 과정 이 한 번 만 동기 화 되 어야 한 다 는 점 을 감안 하여 다음 과 같은 동기 화 는 필요 하지 않 습 니 다. 따라서 인 스 턴 스 가 생 성 되 지 않 았 을 때 만 동기 화 됩 니 다.
        public static LazyLoadWithDoubleCheckSynchronization getInstance(){
            if (instance == null) {
                synchronized (LazyLoadWithDoubleCheckSynchronization.class) {
                    if (instance == null) {
                        instance = new LazyLoadWithDoubleCheckSynchronization();
                    }
                }
            }
            return instance;
        }
    

    장점:
  • 스 레 드 안전
  • 병행 집행 효율 이 높다. 인 스 턴 스 첫 번 째 생 성 과정 에서 만 잠 금 경쟁
  • 이 있다.
    단점:
  • 복잡 실현
  • 적용: 직렬 화 및 반사 파괴 유일 성 을 고려 하지 않 은 장면 에 대해 서 는 이 방법 을 추천 합 니 다.
    1.2.4 내부 클래스
    내부 클래스 를 통 해 유일한 인 스 턴 스 를 가지 고 있 으 며, 클래스 로드 체 제 를 통 해 게 으 른 로드 와 스 레 드 안전 을 확보 합 니 다.
    /**
     * @Author: kkyeer
     * @Description:    3,           ,          ,  
     * @Date:Created in 14:57 2019/6/24
     * @Modified By:
     */
    class LazyLoadWithInnerClass implements Singleton{
        private LazyLoadWithInnerClass(){}
    
        private static class Inner{
            static LazyLoadWithInnerClass instance = new LazyLoadWithInnerClass();
        }
    
        public static LazyLoadWithInnerClass getInstance(){
            return Inner.instance;
        }
    }
    

    장점:
  • 스 레 드 안전
  • 게 으 른 로드
  • 단점:
  • 복잡 실현
  • 적용: 이전 이중 검사 에 비해 한 번 (또는 여러 번) 주소 지정 비용 이 많 고 사용 을 추천 하지 않 습 니 다.
    1.2.5 매 거
    매 거 진 실현 사례 를 통 해 이 방식 을 사용 하 는 것 을 추천 합 니 다. 여러 차원 에서 안전 을 보장 할 수 있 습 니 다.
  • 스 레 드 안전
  • 직렬 화 는 유일 성 을 파괴 하지 않 는 다
  • 반사 호출 이 유일 성 을 파괴 하지 않 음
  • 단순 실현
  • 다음 과 같이 구현:
    enum LazyLoadWithEnum implements Singleton{
        INSTANCE;
        Singleton getInstance(){
            return INSTANCE;
        }
    }
    

    2. 기타 생 성 대상 방식 이 단일 사례 에 대한 유일한 파괴
    단일 모드 의 핵심 은 설 정 된 컨 텍스트 에서 지정 한 인 스 턴 스 는 하나 입 니 다. 이 컨 텍스트 는 수요 에 따라 JVM, 같은 SpringContext 등 을 말 할 수 있 습 니 다. 그러나 우 리 는 모두 배 웠 습 니 다. 대상 을 만 드 는 데 4 가지 방식 이 있 습 니 다.
  • new 키워드: new Object()
  • 대상 의 반 서열 화: objectInputStream.readObject()
  • 반사 호출: Object.class.getDeclaredConstructor().newInstance()
  • clone 방법: obj.clone()
  • 상술 한 단일 사례 실현 에서 구조 기의 사유 화 를 고려 하여 사용자 가 new 새로운 대상 의 방식 으로 유일 성 을 파괴 할 수 없 도록 했 지만 다른 세 가지 방식 을 통 해 다른 사례 를 얻 고 단일 모델 의 유일 성 을 파괴 할 수 있다.
    2.1 clone 방법 은 단일 대상 을 따로 만 들 고 단일 사례 의 유일 성 을 파괴 합 니 다.
    clone 방법 은 Object 의 방법 으로 이론 적 으로 모든 대상 을 계승 합 니 다. 그러나 이 방법 은 proctected 방법 이 고 반드시 명시 적 인 implement Cloneable 인터페이스 가 필요 합 니 다. 다시 말 하면 본 류 (또는 부모 류) 의 명시 적 으로 clone 방법 을 실현 하고 이 를 Public 권한 제한 으로 확대 해 야 하기 때문에 clone 방법 은 단일 모델 의 유일 성 을 파괴 할 수 있 습 니 다.그러나 더 많은 것 은 단일 클래스 를 정의 할 때 override clone 방법 을 정의 할 때 발생 하 는 오류 이기 때문에 토론 하지 않 습 니 다.
    2.2 대상 의 반 직렬 화 파괴 사례 의 유일 성
    비 Enum 의 단일 사례 실현 에 있어 대상 의 반 직렬 화 는 단일 모델 의 유일 성 을 파괴 할 수 있다.
    private static void testSerialization(){
            Singleton created = LazyLoadWithInnerClass.getInstance();
            System.out.println(created.hashCode());
            File testFile = new File("obj.txt");
            try {
                ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(testFile));
                objectOutputStream.writeObject(created);
                objectOutputStream.close();
    
                ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(testFile));
                Singleton dematerializedObject = (Singleton) objectInputStream.readObject();
                objectInputStream.close();
                System.out.println(dematerializedObject.hashCode());
                Assertions.assertTrue(dematerializedObject == created,"        ");
            } catch (IOException | ClassNotFoundException e) {
                e.printStackTrace();
            } finally {
                testFile.delete();
            }
        }
    

    실행 결과:
    1995265320
    1880587981
    Exception in thread "main" java.lang.AssertionError:         
        at utils.Assertions.assertTrue(Assertions.java:27)
        at design.pattern.singleton.TestCase.testSerialization(TestCase.java:116)
        at design.pattern.singleton.TestCase.main(TestCase.java:25)
    

    2.2.1 소스 코드 분석
    Object InputStream. readObject 방법 내부 에 서 는 역 직렬 화 대상 의 유형 을 판단 합 니 다. 일반 대상 (비 String, Class, * Object StreamClass, array, or enum constant) 에 대해 서 는 다음 방법 으로 역 직렬 화 합 니 다.
     private Object readOrdinaryObject(boolean unshared)
            throws IOException
        {
            //  
            // ↓↓↓↓↓↓↓↓↓      ↓↓↓↓↓↓↓↓↓↓
            obj = desc.isInstantiable() ? desc.newInstance() : null;
            //  
            // ↓↓↓↓↓↓↓↓↓  readResolve    ↓↓↓↓↓↓↓↓↓↓
            if (obj != null &&
                handles.lookupException(passHandle) == null &&
                desc.hasReadResolveMethod())
            {
                Object rep = desc.invokeReadResolve(obj);
                //  
                if (rep != obj) {
                    //  
                    handles.setObject(passHandle, obj = rep);
                }
            }
            return obj;
        }
    

    프로 세 스:
  • 공공 무 참 구조 기 를 실례 화 할 수 있 는 대상 에 대해 Object StreamClass 를 호출 하 는 new Instance 방법:
    Object newInstance()
        throws InstantiationException, InvocationTargetException,
            UnsupportedOperationException
    {
        //  
        return cons.newInstance();
        //  
    }
    
    변수 cons private Constructor> cons; 는 안전 검사 부분 을 무시 하고 실제 반 사 를 통 해 새로운 인 스 턴 스 대상 을 만 듭 니 다. 이 새로 만 든 대상 을 최종 결과 로 하면 단일 사례 의 유일 성
  • 을 파괴 합 니 다.
  • 목표 클래스 가 readResolve 방법 을 실현 하면 readResolve 방법 을 호출 하고 되 돌아 오 는 결과 로 이전 결 과 를 덮어 씁 니 다. 따라서 직렬 화 파 괴 를 피 하 는 유일한 사고방식 은 수 동 으로 readResolve 방법 을 실현 하 는 것 입 니 다.
    private Object readResolve(){
        return Inner.instance;
    }
    
  • 2.3 반사 호출 파괴 사례 의 유일 성
    상기 반 직렬 화 된 소스 코드 분석 과 유사 합 니 다. class 대상 의 new Instance 방법 이나 Constructor 대상 을 가 져 와 인 스 턴 스 를 만 들 때 새로운 인 스 턴 스 를 다시 생 성하 여 단일 사례 의 유일 성 을 파괴 합 니 다. 물론 구조 기 에서 하나의 flag 변 수 를 유지 하고 여러 번 구조 할 때 이상 을 던 질 수 있 습 니 다 (어느 정도)이 문 제 를 피하 기:
        private static boolean initFlag = false;
        private LazyLoadWithDoubleCheckSynchronization(){
            if (initFlag) {
                throw new RuntimeException("          ,        ");
            }
            initFlag = true;
            //       
        }
    

    2.4 매 거 진 을 사용 하여 직렬 화 와 반사 과정 에서 단일 사례 에 대한 파 괴 를 피한다.
    매 거 진 을 사용 하여 단일 모델 을 실현 하면 직렬 화 와 반사 과정 에서 단일 사례 에 대한 파 괴 를 방지 할 수 있다.
    2.4.1 단일 사례 모델 은 직렬 화 과정 에서 단일 사례 에 대한 유일한 파 괴 를 피한다.
    단일 사례 의 반 직렬 화 에 대해 흐름 분석 대상 과정 에서 다음 과 같은 방법 을 호출 합 니 다.
    private Enum> readEnum(boolean unshared) throws IOException {
            //  
            String name = readString(false);
            Enum> result = null;
            Class> cl = desc.forClass();
            if (cl != null) {
                Enum> en = Enum.valueOf((Class)cl, name);
                result = en;
                //  
            }
            return result;
        }
    

    보 이 는 스 트림 은 매 거 진 name, 반 직렬 화 시 name 에 따라 Enum 의 value Of 방법 을 호출 하여 JVM 이 초기 화 된 인 스 턴 스 를 가 져 옵 니 다. 따라서 단일 모드 는 매 거 진 을 사용 하여 이 루어 집 니 다. 반 직렬 화 는 단일 사례 의 유일 성 을 파괴 하지 않도록 보장 할 수 있 습 니 다.
    2.4.2 단일 모델 은 반사 파괴 사례 의 유일 성 을 피한다.
    매 거 진 클래스 는 반사 호출 을 할 수 없습니다. 실제 적 으로 아래 코드 를 사용 하여 반사 적 으로 매 거 진 인 스 턴 스 를 만 드 는 것 을 고려 합 니 다.
        private static void testReflection()  {
            try {
                Singleton created = LazyLoadWithEnum.INSTANCE.getInstance();
                Constructor constructor = LazyLoadWithEnum.class.getDeclaredConstructor(String.class, int.class);
                constructor.setAccessible(true);
                Singleton instanceWithReflection = constructor.newInstance();
    
    
                System.out.println(created.hashCode());
                System.out.println(instanceWithReflection.hashCode());
                Assertions.assertTrue(instanceWithReflection == created,"         ");
            } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    

    매 거 진 Constructor 대상 획득 과 new Instance 방법 은 모두 일반 클래스 와 다 릅 니 다.
  • 매 거 진 클래스 는 빈 구조 방법 이 있 는 것 처럼 보이 지만 그렇지 않 습 니 다. 다음은 [DJ Java Decompiler 3.12] 를 사용 하여 반 컴 파일 된 매 거 진 대응 하 는 class 파일 입 니 다.
        final class LazyLoadWithEnum extends Enum
        implements Singleton
    {
    
        public static LazyLoadWithEnum[] values()
        {
            return (LazyLoadWithEnum[])$VALUES.clone();
        }
    
        public static LazyLoadWithEnum valueOf(String name)
        {
            return (LazyLoadWithEnum)Enum.valueOf(design/pattern/singleton/LazyLoadWithEnum, name);
        }
    
        private LazyLoadWithEnum(String s, int i)
        {
            super(s, i);
        }
    
        Singleton getInstance()
        {
            return INSTANCE;
        }
    
        public static final LazyLoadWithEnum INSTANCE;
        private static final LazyLoadWithEnum $VALUES[];
    
        static 
        {
            INSTANCE = new LazyLoadWithEnum("INSTANCE", 0);
            $VALUES = (new LazyLoadWithEnum[] {
                INSTANCE
            });
        }
    }
    
    관찰 한 결과 이러한 정의 되 지 않 은 무 참 구조 기 는 private LazyLoadWithEnum(String s, int i) 입 니 다. 이 로 인해 구조 기 를 가 져 올 때 매개 변수 목록 을 (String, int)
  • 로 지정 해 야 합 니 다.
  • Constructor 의 new Instance () 방법 을 호출 할 때 매 거 진 유형 이 라면 이상 을 던 집 니 다.
  •     public T newInstance(Object ... initargs)
            throws InstantiationException, IllegalAccessException,
                   IllegalArgumentException, InvocationTargetException
        {
            //  
            if ((clazz.getModifiers() & Modifier.ENUM) != 0)
                throw new IllegalArgumentException("Cannot reflectively create enum objects");
            //  
        }
    

    따라서 Enum 을 사용 하여 단일 사례 를 실현 하면 반사 조 때문에 단일 사례 의 유일 성 을 파괴 하지 않 을 것 을 보증 할 수 있다.
    3. 참고
  • 참조 1
  • 참조 2
  • 좋은 웹페이지 즐겨찾기