LoadingCache实战expireAfterWrites和refreshAfterWrites

平时工作中如果需要用到本地缓存的时候,相信大家的首选应该大多数是LoadingCache,毕竟Google出品,必属精品。常规的一些缓存场景LoadingCache都能应付,用过LoadingCache的朋友应该也都知道LoadingCache提供两种刷新机制,分别是expireAfterWrites和refreshAfterWrites,本文就用两个简洁明了的例子来说明下这两种刷新机制的用法。

第一段代码是测试expireAfterWrites,实现了CacheLoader的load方法,load方法中等待了10s后返回缓存值"China",另外启动两个线程每隔1s获取一次缓存

人狠话不多,直接上代码。

public class LoadingCacheExpiryTest {

    public LoadingCache<String, String> loadingCache;
    private ExecutorService executorService = Executors.newFixedThreadPool(5);

    public LoadingCacheExpiryTest() {
        loadingCache = CacheBuilder.newBuilder().expireAfterWrite(5, TimeUnit.SECONDS).build(
            new CacheLoader<String, String>() {
                private AtomicInteger count = new AtomicInteger(1);
                private AtomicInteger reloadCount = new AtomicInteger(1);

                @Override
                public String load(String key) throws Exception {
                    System.out.println("Load Value " + count.get() + " time(s)");
                    for (int i = 0; i < 10; i++) {
                        System.out.println("Load Value for " + i + " second(s)");
                        Thread.currentThread().sleep(1000);
                    }
                    count.incrementAndGet();
                    return "China";
                }

                @Override
                public ListenableFuture<String> reload(String key, String oldValue) throws Exception {
                    System.out.println("Reload for " + reloadCount.get() + " time(s)");
                    ListenableFutureTask<String> futureTask = ListenableFutureTask.create(() -> {
                        for (int i = 0; i < 10; i++) {
                            System.out.println("Reload Value for " + i + " second(s)");
                            Thread.currentThread().sleep(1000);
                        }
                        int count = reloadCount.incrementAndGet();
                        return "China" + count;
                    });
                    executorService.execute(futureTask);
                    return futureTask;
                }
            });
    }

    public static void main(String[] args) {
        LoadingCacheExpiryTest test = new LoadingCacheExpiryTest();
        Runnable runnable1 = () -> {
            for (int i = 0; i < 100; i++) {
                try {
                    System.out.println("Runnable1 Before Get Cache");
                    System.out.println("Runnable1 " + test.loadingCache.get("Country"));
                    System.out.println("Runnable1 After Get Cache");
                    Thread.currentThread().sleep(1000);
                } catch (ExecutionException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        Runnable runnable2 = () -> {
            for (int i = 0; i < 100; i++) {
                try {
                    System.out.println("Runnable2 Before Get Cache");
                    System.out.println("Runnable2 " + test.loadingCache.get("Country"));
                    System.out.println("Runnable2 After Get Cache");
                    Thread.currentThread().sleep(1000);
                } catch (ExecutionException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        Thread thread1 = new Thread(runnable1);
        Thread thread2 = new Thread(runnable2);
        thread1.start();
        thread2.start();
    }
}

测试结果:

Runnable2 Before Get Cache 
Runnable1 Before Get Cache
Load Value 1 time(s)        --进入load方法第一次获取缓存值,这里可以看出加载缓存是lazy load
Load Value for 0 second(s)
Load Value for 1 second(s)
Load Value for 2 second(s)
Load Value for 3 second(s)
Load Value for 4 second(s)
Load Value for 5 second(s)
Load Value for 6 second(s)
Load Value for 7 second(s)
Load Value for 8 second(s)
Load Value for 9 second(s) --缓存值获取完毕
Runnable2 China
Runnable2 After Get Cache  --线程2获取到缓存值
Runnable1 China
Runnable1 After Get Cache
Runnable1 Before Get Cache
Runnable2 Before Get Cache
Runnable1 China            --线程1获取到缓存值
Runnable1 After Get Cache
Runnable2 China
Runnable2 After Get Cache
Runnable1 Before Get Cache
Runnable2 Before Get Cache
Runnable2 China
Runnable2 After Get Cache
Runnable1 China
Runnable1 After Get Cache
Runnable2 Before Get Cache
Runnable1 Before Get Cache
Runnable2 China
Runnable2 After Get Cache
Runnable1 China
Runnable1 After Get Cache
Runnable1 Before Get Cache
Runnable2 Before Get Cache
Runnable1 China
Runnable1 After Get Cache
Runnable2 China
Runnable2 After Get Cache
Runnable2 Before Get Cache
Runnable1 Before Get Cache
Load Value 2 time(s)      --到了缓存5s过期时间,重新调用load加载
Load Value for 0 second(s)
Load Value for 1 second(s)
Load Value for 2 second(s)
Load Value for 3 second(s)
Load Value for 4 second(s)
Load Value for 5 second(s)
Load Value for 6 second(s)
Load Value for 7 second(s)
Load Value for 8 second(s)
Load Value for 9 second(s)--第二次缓存值获取完成,可以看到这段过程中线程1和线程2都没有输出值,说明都block住了
Runnable1 China           --线程1获取到缓存值
Runnable1 After Get Cache 
Runnable2 China           --线程1获取到缓存值
Runnable2 After Get Cache
Runnable2 Before Get Cache

结论1
使用了expireAfterWrites之后,每次缓存失效LoadingCache都会去调用我们实现的load方法去重新加载缓存,在加载期间,所有线程对该缓存key的访问都将被block。所以如果实际加载缓存需要较长时间的话,这种方式不太适用。从代码中还可以看到,即使在CacheLoader实现了reload方法,也不会被调用,因为reload只有当设置了refreshAfterWrites时才会被调用。

接下去我们再测试下refreshAfterWrites
话多人不狠,继续上代码
其实和第一段代码基本一致,只是使用了refreshAfterWrites

public class LoadingCacheRefreshTest {

    public LoadingCache<String, String> loadingCache;
    private ExecutorService executorService = Executors.newFixedThreadPool(5);

    public LoadingCacheRefreshTest() {
        loadingCache = CacheBuilder.newBuilder().refreshAfterWrite(5, TimeUnit.SECONDS).build(
            new CacheLoader<String, String>() {
                private AtomicInteger count = new AtomicInteger(1);
                private AtomicInteger reloadCount = new AtomicInteger(1);

                @Override
                public String load(String key) throws Exception {
                    System.out.println("Load Value " + count.get() + " time(s)");
                    for (int i = 0; i < 10; i++) {
                        System.out.println("Load Value for " + i + " second(s)");
                        Thread.currentThread().sleep(1000);
                    }
                    count.incrementAndGet();
                    return "China";
                }

                @Override
                public ListenableFuture<String> reload(String key, String oldValue) throws Exception {
                    System.out.println("Reload for " + reloadCount.get() + " time(s)");
                    ListenableFutureTask<String> futureTask = ListenableFutureTask.create(() -> {
                        for (int i = 0; i < 10; i++) {
                            System.out.println("Reload Value for " + i + " second(s)");
                            Thread.currentThread().sleep(1000);
                        }
                        int count = reloadCount.incrementAndGet();
                        return "China" + count;
                    });
                    executorService.execute(futureTask);
                    return futureTask;
                }
            });
    }

    public static void main(String[] args) {
        LoadingCacheRefreshTest test = new LoadingCacheRefreshTest();
        Runnable runnable1 = () -> {
            for (int i = 0; i < 100; i++) {
                try {
                    System.out.println("Runnable1 Before Get Cache");
                    System.out.println("Runnable1 " + test.loadingCache.get("Country"));
                    System.out.println("Runnable1 After Get Cache");
                    Thread.currentThread().sleep(1000);
                } catch (ExecutionException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        Runnable runnable2 = () -> {
            for (int i = 0; i < 100; i++) {
                try {
                    System.out.println("Runnable2 Before Get Cache");
                    System.out.println("Runnable2 " + test.loadingCache.get("Country"));
                    System.out.println("Runnable2 After Get Cache");
                    Thread.currentThread().sleep(1000);
                } catch (ExecutionException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        Thread thread1 = new Thread(runnable1);
        Thread thread2 = new Thread(runnable2);
        thread1.start();
        thread2.start();
    }
}

测试结果

Runnable1 Before Get Cache
Runnable2 Before Get Cache
Load Value 1 time(s)        --第一次加载缓存,调用load方法
Load Value for 0 second(s)
Load Value for 1 second(s)
Load Value for 2 second(s)
Load Value for 3 second(s)
Load Value for 4 second(s)
Load Value for 5 second(s)
Load Value for 6 second(s)
Load Value for 7 second(s)
Load Value for 8 second(s)
Load Value for 9 second(s)
Runnable2 China            --线程2获取到缓存值China
Runnable2 After Get Cache
Runnable1 China            --线程1获取到缓存值China
Runnable1 After Get Cache
Runnable1 Before Get Cache
Runnable2 Before Get Cache
Runnable1 China
Runnable1 After Get Cache
Runnable2 China
Runnable2 After Get Cache
Runnable2 Before Get Cache
Runnable1 Before Get Cache
Runnable2 China
Runnable2 After Get Cache
Runnable1 China
Runnable1 After Get Cache
Runnable1 Before Get Cache
Runnable2 Before Get Cache
Runnable1 China
Runnable1 After Get Cache
Runnable2 China
Runnable2 After Get Cache
Runnable2 Before Get Cache
Runnable1 Before Get Cache
Runnable2 China
Runnable2 After Get Cache
Runnable1 China
Runnable1 After Get Cache
Runnable2 Before Get Cache
Runnable1 Before Get Cache
Reload for 1 time(s)       --缓存过期,调用realod方法刷新缓存
Runnable1 China            --注意看,这里的线程1仍然可以获取到China,而不会被block
Runnable1 After Get Cache
Reload Value for 0 second(s)
Runnable2 China            --注意看,这里的线程2仍然可以获取到China,而不会被block
Runnable2 After Get Cache
Runnable1 Before Get Cache
Runnable2 Before Get Cache
Runnable2 China
Runnable2 After Get Cache
Reload Value for 1 second(s)--缓存加载ing
Runnable1 China
Runnable1 After Get Cache
Runnable2 Before Get Cache
Runnable1 Before Get Cache
Runnable1 China            --注意看,这里的线程1仍然可以获取到China,而不会被block
Runnable1 After Get Cache
Reload Value for 2 second(s)--缓存加载ing
Runnable2 China             --注意看,这里的线程2仍然可以获取到China,而不会被block
Runnable2 After Get Cache
Runnable1 Before Get Cache
Runnable2 Before Get Cache
Reload Value for 3 second(s)
Runnable2 China
Runnable2 After Get Cache
Runnable1 China
Runnable1 After Get Cache
Runnable1 Before Get Cache
Runnable1 China
Runnable1 After Get Cache
Runnable2 Before Get Cache
Runnable2 China
Runnable2 After Get Cache
Reload Value for 4 second(s)
Runnable1 Before Get Cache
Runnable2 Before Get Cache
Runnable1 China
Runnable1 After Get Cache
Reload Value for 5 second(s)
Runnable2 China
Runnable2 After Get Cache
Runnable1 Before Get Cache
Reload Value for 6 second(s)
Runnable2 Before Get Cache
Runnable1 China
Runnable1 After Get Cache
Runnable2 China
Runnable2 After Get Cache
Runnable2 Before Get Cache
Reload Value for 7 second(s)
Runnable1 Before Get Cache
Runnable2 China
Runnable2 After Get Cache
Runnable1 China
Runnable1 After Get Cache
Runnable2 Before Get Cache
Runnable1 Before Get Cache
Reload Value for 8 second(s)
Runnable1 China
Runnable1 After Get Cache
Runnable2 China
Runnable2 After Get Cache
Runnable2 Before Get Cache
Runnable1 Before Get Cache
Reload Value for 9 second(s)
Runnable1 China
Runnable1 After Get Cache
Runnable2 China
Runnable2 After Get Cache
Runnable2 Before Get Cache
Runnable1 Before Get Cache
Runnable2 China
Runnable2 After Get Cache
Runnable1 China
Runnable1 After Get Cache
Runnable2 Before Get Cache
Runnable1 Before Get Cache
Runnable2 China2           --缓存加载完毕,线程2获取到了最新的缓存值China2
Runnable2 After Get Cache
Runnable1 China2           --缓存加载完毕,线程1获取到了最新的缓存值China2
Runnable1 After Get Cache
Runnable2 Before Get Cache
Runnable1 Before Get Cache
Runnable2 China2
Runnable2 After Get Cache
Runnable1 China2

结论2
注意看代码,适用了refreshAfterWrites之后,需要自己实现CacheLoader的reload方法,在方法中创建一个ListenableFutureTask,然后将这个task提交给线程池去异步执行,reload方法最后返回这个ListenableFutureTask。这样做之后,缓存失效后重新加载就变成了异步,加载期间尝试获取缓存的线程也都不会被block,而是获取到加载之前的值。当加载完毕之后,各个线程就能取到最新值了。

从这两个例子可以看出,对于互联网高并发的场景,refreshAfterWrites这种使用异步刷新缓存的方法应该是我们首先考虑的,取到一个过期的旧值总比大量线程全都被block要好,后者可能会导致请求大量堆积,连接数不够等一些列问题。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,082评论 1 32
  • 限时0元促销。原价100元。只为手快的您准备。不要错过“超级抠图”,一键抠图工具,从此告别PS的蜗牛抠图。现正半价...
    云端漫步的太阳阅读 448评论 0 0
  • 妹妹离婚了,姐姐姐夫很开心,妹妹带着女儿苦命的干活,姐姐帮忙养着女儿,妹妹一人在厂子带工人风里雨里的干活。 ...
    痴情男子阅读 256评论 0 0
  • 春华容易逝, 夏木荫犹深。 已是知秋季, 何妨月下吟。
    江南烟雨阅读 315评论 5 3