0

次の簡単なプログラムを考えてみましょう:

import java.util.concurrent.ExecutionException;
import java.util.logging.Logger;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;

public class Main {

    final static Logger logger = Logger.getLogger(Main.class.getName());

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        final LoadingCache<Integer, String> cache = CacheBuilder.newBuilder().build(
                new CacheLoader<Integer, String>() {

                    @Override
                    public String load(Integer arg0) throws Exception {
                        logger.info("Cache builder START: " + arg0);
                        Thread.sleep(4000);
                        logger.info("Cache builder FINISH: " + arg0);
                        return "This is what CacheBuilder returned for key " + arg0;
                    }
                });

        Thread getterThread = new Getter(cache);
        getterThread.start();

        Thread setterThread = new Setter(cache);
        setterThread.start();

        getterThread.join();
        setterThread.join();

        logger.info("Finally in cache we have: " + cache.get(1));

    }

    private static final class Getter extends Thread {
        private final LoadingCache<Integer, String> cache;

        private Getter(LoadingCache<Integer, String> cache) {
            this.cache = cache;
        }

        @Override
        public void run() {
            try {
                logger.info("Getter thread reads 1st time " + cache.get(1)
                        + "  <<<<<<<<<< WHAT !?!");
                // allow the setter to put the value
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                logger.info("Getter thread reads 2nd time " + cache.get(1));
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    }

    private static final class Setter extends Thread {
        private final LoadingCache<Integer, String> cache;

        private Setter(LoadingCache<Integer, String> cache) {
            this.cache = cache;
        }

        @Override
        public void run() {
            try {
                // deliberately wait to allow the Getter thread
                // trigger cache loading
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                cache.put(1, "This isn't where I parked my car!");
                logger.info("Setter thread now reads: " + cache.get(1));
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    }
}

出力は次のとおりです。

2013-11-08 15:24:32 INFO Main$1 load Cache builder START: 1
2013-11-08 15:24:32 INFO Main$Setter run Setter thread now reads: This isn't where I parked my car!
2013-11-08 15:24:36 INFO Main$1 load Cache builder FINISH: 1
2013-11-08 15:24:36 INFO Main$Getter run Getter thread reads 1st time This is what CacheBuilder returned for key 1  <<<<<<<<<< WHAT !?!
2013-11-08 15:24:37 INFO Main$Getter run Getter thread reads 2nd time This isn't where I parked my car!
2013-11-08 15:24:37 INFO Main main Finally in cache we have: This isn't where I parked my car!

Getterスレッドで「これはキー1に対してCacheBuilderが返したものです」を取得しています。明らかにこれは、Getter によって呼び出された get(1) がキャッシュの読み込みをトリガーするためですが、その間に Setter スレッドが来て、キー 1 に別の値を設定するためです。車を止めた場所じゃない!」(Getter が 1 の値を取得するのは 2 回目です)。

私は何か見落としてますか ?

前もって感謝します

4

1 に答える 1

0

はい。キャッシュの内部データ構造は同期され、そのような汚染から保護されます。頭の中のモデルは次のようになります。スレッドがキャッシュを使用している限り、独自のコピーがあります。

したがって、最初のスレッドがキャッシュの読み込みをトリガーします。Guava はキャッシュを「複製」します (内部的には、スレッド 1 が見ている構造を誰も変更できないようにするだけです)。4 秒後、スレッドはキャッシュの読み込みによって返された結果を取得します。その間に他のスレッドがいくつ値を変更しても (それらはすべて独自の「コピー」を取得して変更します)。

スレッド 1 が終了すると、キャッシュが更新されます。これで、スレッド 2 からの変更がスレッド 1 に表示されます。

于 2013-11-08T14:57:46.920 に答える