본문 바로가기

STUDY/이펙티브자바

아이템 83: 지연 초기화는 신중히 사용하라

지연 초기화

 

public class FileUtil {
    private File oldfile = new File("oldfile");
    ...
}

여기 FileUtil 클래스가 있다. 이 클래스의 oldfile 필드가 프로그램이 100번 실행될 때 한 번 사용될 까 말까하는 필드라면 어떨까? 100번 실행되는 동안 사용하지도 않을 oldfile을 계속 불러와서 초기화 할 것이고 이는 명백한 리소스 낭비이다. 지연 초기화는 위와 같은 문제에 대한 해답이 될 수 있다. 자주 사용되지 않는 필드를 프로그램이 시작할 때가 아니라 비로소 사용될 때 초기화 하는 것이다.

 

public class OldFileUtil {
    private File oldfile = null;
    ...
    public File getOldFile() {
        if(oldfile == null) {
            oldfile = new File("oldfile");
        }
        return oldfile;
    }
    ...
}

 

 


지연 초기화의 동시성 이슈

 

public class OldFileUtil {
    private File oldfile = null;
    ...
    public File getOldFile() {
        if(oldfile == null) {
            oldfile = new File("oldfile");
            // 한번만 초기화 해야하는 코드
            new FileInputStream(oldfile).getChannel().lock();
        }
        return oldfile;
    }
    ...
}

//thread1
oldFileUtil.getOldFile();
//thread2
oldFileUtil.getOldFile();

멀티스레드 환경에서의 지연 초기화는 동시성 이슈를 유발 한다. 한번만 초기화 해야하는 필드를 두개의 스레드가 동시에 초기화 할 수 있기 때문이다. 지연 초기화의 동시성 이슈를 해결하기 위해서는 초기화가 수행되는 코드를 동기화 시켜주어야 한다.

 

public syncronized File getOldFile() {
    if(oldfile == null) {
        oldfile = new File("oldfile");
        new FileInputStream(oldfile).getChannel().lock();
    }
    return oldfile;
}

위 코드는 초기화가 일어나는 getOldFile 메소드에 synchronized 키워드를 붙여서 동시성 이슈를 해결하였다. 하지만 단순히 필드를 반환하기만 하면 되는 get 메소드를 synchronized 구문으로 묶는 것은 성능에 있어서 비효율적이다.

 

public File getOldFile() {
    if(oldfile != null)
        return oldfile;
	
    synchronized (this) {
        if(oldfile == null) {
    	    oldfile = new File("oldfile");
            new FileInputStream(oldfile).getChannel().lock();
        }
    } 
    return oldfile;
}

synchronized로 묶인 동기화 구문 전에 oldfile이 null이 아니면 반환하는 코드를 넣어주어서 성능상의 비효율을 일부 개선하였다. 하지만 이제 깨달아야 한다. 단순한 get메소드도 이렇게 복잡해져야할 정도로 지연초기화가 정말 필요한 것인가?

 

 

728x90