MyCache

package com.sometest;

import java.time.Duration;
import java.time.Instant;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 缓存
 **/
public class MyCache<T> {
    //数据map
    public Map<String, CacheObj<T>> dataMap = new ConcurrentHashMap<>();

    //过期时间采集map
    public Map<Long, Long> expireMap = new ConcurrentHashMap<>();

    //最大限制长度
    long SIZE_LIMIT;

    //触发过期清理长度
    long CTL;

    //是否已有子线程进行清理
    AtomicInteger cleaning;

    //数据源
    DataSrc<T> dataSrc;

    //过期秒数(每个kv)
    long expireSecond;

    //防大规模同时过期/雪崩
    int expireBalance;

    //缓存创建时间
    Instant createTime;


    public MyCache(DataSrc dataSrc) {
        this(0, dataSrc, 1800, -1);
    }


    public MyCache(long size, DataSrc dataSrc, long expireSecond, int expireBalance) {
        this.SIZE_LIMIT = size;
        this.CTL = Math.round(size * 0.6);
        cleaning = new AtomicInteger(0);
        this.dataSrc = dataSrc;
        this.expireSecond = expireSecond;
        this.expireBalance = expireBalance;
    }


    public T getValue(String id) {
        CacheObj<T> cacheObj = dataMap.get(id);
        if (cacheObj != null && !expired(cacheObj) && cacheObj.state.get() == OK_STATE) {
            cacheObj.hit.addAndGet(1);
            return cacheObj.value;
        }

        if (!limit(dataMap)) {
            tryClean();
            return dataSrc.getFromSrc(id);
        }

        CacheObj<T> obj = new CacheObj();

        if (dataMap.putIfAbsent(id, obj)==null) {
            obj.value = dataSrc.getFromSrc(id);
            obj.ttl = expireTime(0);
            obj.state.set(OK_STATE);
        } else {
            while (obj.state.get() == INIT_STATE) {
            }
        }


        if (dataMap.size() > CTL) {
            tryClean();
        }

        return obj.value;
    }

    private Instant expireTime(int offset) {
        Instant expired = Instant.now().plusSeconds(expireSecond).plusMillis(offset);
        if (expireBalance <= 0) {
            return expired;
        }

        long l = Duration.between(createTime, expired).toMillis();

        Long count = expireMap.compute(l, (k, v) -> {
            if (v == null) {
                return 1L;
            } else if (v < expireBalance) {
                return v + 1;
            } else {
                return v;
            }
        });

        if (count == expireBalance) {
            return expireTime(new Random().nextInt(50));
        }

        return expired;
    }

    private boolean limit(Map<String, CacheObj<T>> map) {
        if (SIZE_LIMIT > 0 && map.size() >= SIZE_LIMIT) {
            return true;
        }
        return false;
    }

    private void tryClean() {
        if (SIZE_LIMIT <= 0) {
            return;
        }
        if (cleaning.compareAndSet(0, 1)) {
            new Thread(new Runnable() {

                @Override
                public void run() {
                    Iterator<Map.Entry<String, CacheObj<T>>> iterator = dataMap.entrySet().iterator();
                    while (iterator.hasNext()) {
                        Map.Entry<String, CacheObj<T>> next = iterator.next();
                        if (expired(next.getValue())) {
                            invalid(next.getKey());
                        }
                    }
                    cleaning.compareAndSet(1, 0);
                }
            }).start();
        }
    }


    public boolean invalid(String id) {
        CacheObj cacheObj = dataMap.get(id);
        if (cacheObj == null || cacheObj.state.get() != OK_STATE) {
            return false;
        }
        return dataMap.remove(id, cacheObj);
    }


    public boolean expired(CacheObj<T> cacheObj) {
        return cacheObj.ttl.isBefore(Instant.now());
    }


    public static int OK_STATE = 1;
    public static int INIT_STATE = 0;
//    public static int DEL_STATE = -1;

    static class CacheObj<T> {
        T value;
        Instant ttl;
        AtomicInteger state = new AtomicInteger(INIT_STATE);
        AtomicInteger hit = new AtomicInteger(0);
    }

    interface DataSrc<T> {
        T getFromSrc(String id);
    }


    public static void main(String[] args) {
        MyCache<String> cache = new MyCache<>(new DataSrc<String>() {
            @Override
            public String getFromSrc(String id) {
                // from DB
                return id;
            }
        });

        cache.getValue("2");
        cache.getValue("2");
    }

}

Article written by

Please comment with your real name using good manners.

Leave a Reply