このエントリーをはてなブックマークに追加

JavaDoc

Lazy thread-sefe sigleton supplier 遅延で、スレッドセーフで、シングルトンなオブジェクトを作る時に使います。

遅延でシングルトンならこういうコードを書いたことがあると思います。

ただし、これはスレッドセーフではありません。同時に呼ばれると、generateCache中に、何度もcacheが生成されてしまいます。

public Cache getCache(){
    if(cahe==null){
    cache=generateCache();
    }
    return cache;
}

これを、スレッドセーフにしたのが、Suppliersです。Supplierインターフェースを使います。

compose

Functionをを組み合わせて別のsupplierから別の値を返すようにします。

memoize

一度しかget()を呼び出さないsupplierを生成します。

よく間違えるのですが、基本memoizeは初期化時に一度呼ぶだけで、後は、生成された、Supplierのget()を使って値を取り出します。

memoizeWithExpiration

有効期限を指定可能です。簡易なキャッシュとしても使えます。 特に、Cacheが対応していないGWTとかで

ofInstance

常に一定値を返すSupplierを生成します。

supplierFunction

supplierから、値を取り出すFunctionを生成します。

synchronizedSupplier

引数のsupplierをスレッドセーフにします。

supplierをimplementsしただけのクラスをスレッド対応にしたい時に使います。

中身

ダブルチェックロッキングを使って実現しています。

これは、JDK1.4まではbadノウハウでしたが、1.5以降は、violateの効果で正常に動くようです。 via wiki

  @VisibleForTesting
  static class MemoizingSupplier<T> implements Supplier<T>, Serializable {
    final Supplier<T> delegate;
    transient volatile boolean initialized;
    // "value" does not need to be volatile; visibility piggy-backs
    // on volatile read of "initialized".
    transient T value;

    MemoizingSupplier(Supplier<T> delegate) {
      this.delegate = delegate;
    }

    @Override public T get() {
      // A 2-field variant of Double Checked Locking.
      if (!initialized) {
        synchronized (this) {
          if (!initialized) {
            T t = delegate.get();
            value = t;
            initialized = true;
            return t;
          }
        }
      }
      return value;
    }

!=nullの代わりに使う

まあ、スレッドセーフにする必要性がないので、使う必要もないのだけど

public static class ImageUrlDataResizeInfo implements Supplier<Anchor>{
        private Supplier<Anchor> downloadLink=Suppliers.memoize(this);

        @Override
        public Anchor get() {
            Anchor a=generateDownloadLink();
            return a;
        }


        public Anchor getDownloadLink(){
            return downloadLink.get();
        }    
    }

Google App Engineのキャッシュ生成時に

普通にcache==nullで初期化してると、初期化中にアクセスがあると二度呼ばれる危険がある。 ただし、nullもキャッシュしてしまうので、nullの場合は失敗ということで、NullPointerException呼び出して、次回に持ち越しにしてもらう。

public static Cache getCache() throws NullPointerException{
    return memorized.get();
    }

    public static Supplier<Cache> cacheSupplier=new Supplier<Cache>(){
        public Cache get(){
            CacheFactory cacheFactory;
            Cache cache=null;
                try {
                    @SuppressWarnings("rawtypes")
                    Map props = new HashMap();

                    props.put(GCacheFactory.EXPIRATION_DELTA, 3600*24);// expire 1 days cache is always new

                    cacheFactory = CacheManager.getInstance().getCacheFactory();
                    cache = cacheFactory.createCache(props);
                    System.out.println("generate-cache");
                } catch (CacheException e) {
                    e.printStackTrace();
                }

                if(cache==null){
                    throw new NullPointerException("cache is null");
                }
        return cache;
        }
    };

    private static volatile Supplier<Cache> memorized = Suppliers.memoize(cacheSupplier);