caffeine源码分析——如何做到原子性的

2018-09-18  本文已影响0人  黄云斌huangyunbin
caffeine的load put 和invalidate操作都是原子的,这个意思是这3个操作是互斥的,load和put是不能同时执行的,load和invalidate也是不能同时执行的。

先load再invalidate,invalidate操作是要等load操作执行完的。如果load操作执行比较慢,那invalidate操作就要等很久了。

这样做有什么好处呢,可以保证结果完全符合预期。

这种方式和guava是不同的,guava是不阻塞的。
先load再invalidate,invalidate操作是马上执行完的,不需要等待load这种耗时操作执行完。这个其实不合预期的,根本达不到invalidate的效果了。
先load再invalidate,本意是要让当前的load操作失效,但是load操作时间比较长,load操作结束是在invalidate之后了,失效不了这吃load操作。

说概念可能比较抽象,我们来举个例子:
public static void main(String[] args) throws Exception {

        final AtomicInteger atomicInteger = new AtomicInteger();
        final LoadingCache<String, String> cache = CacheBuilder.newBuilder().maximumSize(500)
                .build(new CacheLoader<String, String>() {
                    @Override
                    public String load(String s) throws Exception {
                        Thread.sleep(1000);
                        return atomicInteger.incrementAndGet() + "";
                    }
                });

        cache.get("test");
        cache.invalidate("test");

        new Thread() {
            @Override
            public void run() {
                try {
                    String value = cache.get("test");
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
            }
        }.start();

        new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                long start = System.currentTimeMillis();
                cache.invalidate("test");
                System.out.println("use ms:" + (System.currentTimeMillis() - start));
            }
        }.start();


        Thread.sleep(1200);
        System.out.println("========" + cache.asMap());
        System.out.println("========" + cache.get("test"));

    }

结果是:
use ms:0
========{test=2}
========2

其实我们期望的结果是3的,不能满足我们的期望。

再看看caffeine,这个时候原子性就的好处就得到体现了。
public static void main(String[] args) throws Exception {
        AtomicInteger atomicInteger=new AtomicInteger();
        LoadingCache<String, String> cache = Caffeine.newBuilder().maximumSize(3).build(key -> {
            Thread.sleep(1000);
            return atomicInteger.incrementAndGet()+"";
        });

        cache.get("test");
        cache.invalidate("test");

        new Thread() {
            @Override
            public void run() {
                cache.get("test");

            }
        }.start();

        new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                long start = System.currentTimeMillis();
                cache.invalidate("test");
                System.out.println("use ms:"+(System.currentTimeMillis() - start));
            }
        }.start();


        Thread.sleep(1200);
        System.out.println("========" + cache.asMap());
        System.out.println("========" + cache.get("test"));

    }
结果是:

use ms:802
========{}
========3
这个就是我们想要的结果了,虽然invalidate阻塞了一段时间。

caffeine实现原理

其实原理很简单,caffeine的存储就是ConcurrentHashMap,利用了ConcurrentHashMap自己的node节点锁。
invalidate操作对应的就是remove方法
image.png

可以看到remove是加锁的

而load方法对应的是compute方法
image.png

remappingFunction.apply 这里对应的就是我们具体的load的方法内容

compute方法也是加锁的,key相同的情况下,compute是和remove加的相同的锁的。
上一篇下一篇

猜你喜欢

热点阅读