Javaでの怠惰なロードの正確な実現方法

3392 ワード

1.通常、リロード初期化の典型的な実装方法:
public class LazyInit {

    public static Resource resource;

    public static Resource getResource() {
        if (resource == null) {
            resource = new Resource();
        }
        return resource;
    }
}

単一スレッド・アプリケーションでは、上記の例は、予想通りに動作することを保証します.ただし、マルチスレッドアプリケーションではさまざまな結果が得られる場合があります.予想通りに動作したり、リソースを複数回初期化したり、一部のオブジェクト(一部のオブジェクトを読み込む)をパブリッシュしたりすることができます. だから安全ではない実現方式です.
2. 安全な実現方式
public class LazyInit {

    public static Resource resource;

    public synchronized static Resource getResource() {
        if (resource == null) {
            resource = new Resource();
        }
        return resource;
    }
}

現在、resourceはLazyInitクラスによって保護されています.このメソッドにアクセスし、resourceが初期化されているかどうかを確認できるスレッドは1つしかありません.もしそうであれば、現在のインスタンスを返します.
3. 場合によっては、Resourceインスタンスへのアクセスを高速化するために二重チェックを行う場合があります.
public class LazyInit {

    public static Resource resource;

    public static Resource getResource() {
        if (resource == null) {
            synchronized (LazyInit.class){
                if (resource == null){
                    resource = new Resource();
                }
            }
        }
        return resource;
    }
}

非同期初期化されている場合は、Resourceを確認します. ,そうでなければ、同期して初期化されます.
よく見えますが、(2.安全な実装方法)には及ばないです.この場合、1つのスレッドはResourceの部分的なロードを見ることができます.これはnullとは違いますが、まだ完全に構築されていないことを意味します.
JSR-133によると、Javaは最終フィールドが構築完了する前に「凍結」されることを保証している.Resourceファイルを見てみよう.
public class LazyInit {
    
    public final int FINAL_VALUE;
    public int value;
    
    public Resource(){
        FINAL_VALUE = 1;
        value = 1;
    }
}

LazyInit.getResource()メソッドからResourceのスレッドを取得すると、実際には一部のオブジェクトが取得されます.
... 
int aFinalValue = LazyInit.getResource()。FINAL_VALUE //  1 
int aValue = LazyInit.getResource()。value //   0 
...

二重チェックロック用のソリューションには、揮発性タイプのリソースが必要です.このソリューションを静的フィールドに使用しないことをお勧めします.
public class LazyInit {

    private volatile Resource resource;

    public  Resource getResource() {
        Resource result = resource;
        if (result == null) {
            synchronized (this){
                result = resource;
                if (result == null){
                    resource = new Resource();
                }
            }
        }
        return resource;
    }
}

揮発性保証フィールドは、すべてのスレッドで最新の値を返し、書き込み時にロックします.volatile値は通常の変数に割り当てられているため、チェック時に変更されないことに注意してください. したがって、一時変数に値を割り当てます.
4. 最速初期化方式
public class EagerInit {

    private static Resource resource = new Resource();

    public static   Resource getResource() {
        return resource;
    }
}

最終フィールドは、コンストラクションが完了する前に「フリーズ」されることを約束するだけでなく、静的フィールドもコンストラクション関数がトリガーされる前に初期化する必要があります.
この例は、resourceを使用する前にResourceをインスタンス化することを保証する.
上記の例はスレッドが安全ですが、JVMがすぐに初期化したもので、私たちが望んでいるものではありません.これらの知識を使用することができますが、これは私たちが望んでいるものではありません.
5. Bill Pugh Singleton(単列モード)
JVMは内部静的クラスの処理方法が異なることが実証されています.実際に使用するまでインスタンスオブジェクトを初期化しません.この小さな情報は、スレッドセキュリティの単一インスタンスの初期化の次の例に進みます.
public class LazyInit {

    private static class Holder{
        public static Resource resource = new Resource();
    }
    
    public static Resource getResource(){
        return Holder.resource;
    }
}

これはJavaで最も推奨される遅延初期化方法です.超簡単で、超安全です.