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 线程中得到这个“这是 CacheBuilder 为键 1 返回的内容”。显然,这是因为 Getter 调用的 get(1) 触发了缓存加载,但与此同时,Setter 线程来了并为键 1 放置了一些其他值。我希望它返回与 Setter = "This不是我停车的地方!” (我得到第二次 Getter 检索 1 的值)。

我错过了什么 ?

提前致谢

4

1 回答 1

0

是的。缓存的内部数据结构是同步的,以保护您免受此类污染。你脑海中的模型应该是:只要一个线程在使用缓存,它就有自己的副本。

所以第一个线程触发缓存加载。Guava“克隆”缓存(在内部,它只是确保没有人可以更改线程 1 看到的结构)。4秒后,线程得到缓存加载返回的结果,不管有多少其他线程同时改变这个值(他们都得到自己的“副本”来修改)。

当线程 1 完成时,缓存会自行更新。现在,线程 2 的更改对线程 1 可见。

于 2013-11-08T14:57:46.920 回答