基于ConcurrentMap实现loadingCache

0 概述

一般的缓存策略是缓存没有命中的时候,直接去获取数据然后再放入缓存,这样可能会出现多个请求同时没有命中缓存某个key,同时去取数据,造成资源浪费,loadingCache就是为了解决这个问题。本文基于ConcurrentMap实现loadingCache。

1 具体实现

/**
 * Created by apple on 18/2/23.
 */
public class LoadingCache {

    private static ConcurrentHashMap cache = new ConcurrentHashMap<>();

    public String get(String key) {
        if (key == null) {
            return null;
        }
        Object value = cache.get(key);
        //直接获取到值了
        if (value != null && !(value instanceof FutureTask)) {
            return (String) value;
        }
        return create(key, value);

    }

    @SuppressWarnings("unchecked")
    private String create(String key, Object value) {
        FutureTask futureTask;
        Object result;
        boolean creator = false;
        if (value != null) {
            futureTask = (FutureTask) value;
        } else {
            futureTask = new FutureTask<>(() -> loadData(key));
            //已经存在key对应的value会返回具体的value,否则返回null
            result = cache.putIfAbsent(key, futureTask);
            //首次添加(该key对应的value不存在)
            if (result == null) {
                creator = true;
                futureTask.run();
                //已经存在且正在获取Value如果是FutureTask
            } else if (result instanceof FutureTask) {
                futureTask = (FutureTask) result;
                //已经获取到Value
            }else {
                return (String) result;
            }
        }
        String retValue = null;
        try {
            retValue = futureTask.get();
        } catch (InterruptedException e) {
            throw new IllegalStateException("Interrupted while loading cache", e);
        } catch (ExecutionException e) {
            Throwable cause = e.getCause();
            if (cause instanceof RuntimeException) {
                throw ((RuntimeException) cause);
            }
        }

        if (creator && retValue != null) {
            cache.put(key, retValue);
        }
        return retValue;
    }

    private String loadData(String key) {
        try {
            //这里sleep 为了测试
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return key;
    }


}

获取缓存主要分为以下几种情况
1.value已经存在 2.value 正在获取中(FututreTask)3.value不存在,当前线程去获取具体的数据。对于cache.putIfAbsent(key, futureTask)结果有三种情况1)当前线程成功插入一个FututreTask,说明这个值需要当前线程去获取,最后当前线程需要把获取到具体数据放入缓存2)已经一个FututreTask(别的线程插入的),当前线程只需要调用FutureTask.get()等待别的线程获取到数据3)具体的数据已经存在,这种情况直接返回就好

2 总结

可以看出loadingCache比较适合哪种流量比较高或者获取具体数据需要消耗很多资源的情况。本文中的loadingCache只能在单机的情况下使用,另外还有个问题,当多个线程取获取同一个key的数据时候是需要阻塞等待的,这种情况会占用线程池中的线程。

参考文献:
本文是参考org.springframework.cglib.core.internal.LoadingCache 中的实现。

你可能感兴趣的:(java基础)