Java后端程序员面经(2-1. 缓存之本地缓存)

说明

本文主要基于面试中碰到的问题进行总结分析,可能不全。

应用场景

1. 对数据一致性要求不强,主要是由于现在web服务一般是多机房部署
2. 访问频繁,且更新不频繁的数据,前者体现了缓存的作用减少对DB的压力,后者说明了缓存毕竟是对DB数据的副本,如果经常不一致是肯定不行的
3. 缓存的数据量不宜太大,毕竟单机内存还要分配来提供服务,不能太多给你当缓存使用了

技术思想

1. 需要一个装数据的容器,一般来说就是map
2. 需要实现数据的读取和加载(将DB结果存入缓存)
3. 有一定的策略对缓存数据进行失效(否则会导致长时间数据不一致性)
4. 容器需要设置一个上限,并在达到上限时有一定的策略进行删除(见应用场景第三条)

具体实现

1. 自己实现
    1-1. 申请一个ConcurrentHashMap(满了的策略不好实现)、或者LinkedHashMap(LRU好实现满了的策略,不过要自己加锁作线程安全)用作存储
    1-2. 申请一个Timer()用作定时器,以实现定时对数据进行失效
2. guava localCache
    2-1. 引用
    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>23.0</version>
    </dependency>
    2-2.使用
    private LoadingCache<String, Book> CACHE = CacheBuilder.newBuilder()
            .maximumSize(10)
            .refreshAfterWrite(10, TimeUnit.SECONDS) // 当缓存数据过期的时候,真正去加载数据的线程会阻塞一段时间,其余线程立马返回过期的值
            .expireAfterAccess(15, TimeUnit.SECONDS) // 访问之后多久过期(删除此key的数据)
            .build(
                    new CacheLoader<String, Book>() {
                        @Override
                        public Book load(String name) throws Exception {
                            return bookDao.getByName(name); // 从DB中加载数据
                        }
                    }
            );
    2-3. 调用
    public Book getByName(String name) {
        return CACHE.getUnchecked(name);
    }
    2-4. 参数refreshAfterWrite测试
    private LoadingCache<String, Book> CACHE = CacheBuilder.newBuilder()
            .maximumSize(10)
            .refreshAfterWrite(10, TimeUnit.SECONDS)
            .expireAfterAccess(60, TimeUnit.SECONDS)
            .build(
                    new CacheLoader<String, Book>() {
                        @Override
                        public Book load(String name) throws Exception {
                            Book book = bookDao.getByName(name);
                            System.out.println("c:\t" + LocalTime.now().getSecond() + book);
                            return book;
                        }
                    }
            );
       public Book getByNameWithCallable(String name) {
        Book result = null;
        try {
            result = CACHE.get(name, () -> {
                Book book = bookDao.getByName(name);
                System.out.println("a:\t" + LocalTime.now().getSecond() + book);
                try {
                    Thread.sleep(5000);
                } catch (Exception ex) {

                }
                return book;
            });
            System.out.println("b:\t" + LocalTime.now().getSecond() + result);
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        return result;
    }
    当多条线程调用时,第一条进行加载,其它的返回旧值,而且10s之后被访问就会触发刷新,即输出a那行。
    第一个线程调用输出
    {
    "id":5,
    "name":"wdmyong",
    "date":{
        "year":2017,
        "month":"OCTOBER",
        "chronology":{
            "id":"ISO",
            "calendarType":"iso8601"
        },
        "era":"CE",
        "dayOfYear":290,
        "dayOfWeek":"TUESDAY",
        "leapYear":false,
        "dayOfMonth":17,
        "monthValue":10
    }
}
第二条线程的输出
{
    "id": 4,
    "name": "wdmyong",
    "date": {
        "year": 2017,
        "month": "OCTOBER",
        "chronology": {
            "id": "ISO",
            "calendarType": "iso8601"
        },
        "era": "CE",
        "dayOfYear": 290,
        "dayOfWeek": "TUESDAY",
        "leapYear": false,
        "dayOfMonth": 17,
        "monthValue": 10
    }
}
我的id用了自增,可以看出在第一条线程计算的时候返回的是原来的id为4,等自己计算完之后,第一条线程返回的是id为5的数据。
2-4. 上述过程应该还可以完全异步,即第一条计算线程也返回原值,让它异步计算自己计算去,后续待研究。
3. 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值