缓存,在我们日常开发中是必不可少的一种解决性能问题的方法。简单的说,cache 就是为了提升系统性能而开辟的一块内存空间。
缓存的主要作用是暂时在内存中保存业务系统的数据处理结果,并且等待下次访问使用。在日常开发的很多场合,由于受限于硬盘IO的性能或者我们自身业务系统的数据处理和获取可能非常费时,当我们发现我们的系统这个数据请求量很大的时候,频繁的IO和频繁的逻辑处理会导致硬盘和CPU资源的瓶颈出现。缓存的作用就是将这些来自不易的数据保存在内存中,当有其他线程或者客户端需要查询相同的数据资源时,直接从缓存的内存块中返回数据,这样不但可以提高系统的响应时间,同时也可以节省对这些数据的处理流程的资源消耗,整体上来说,系统性能会有大大的提升。
缓存在很多系统和架构中都用广泛的应用,例如:
1.CPU缓存
2.操作系统缓存
3.本地缓存
4.分布式缓存
5.HTTP缓存
6.数据库缓存
等等,可以说在计算机和网络领域,缓存无处不在。可以这么说,只要有硬件性能不对等,涉及到网络传输的地方都会有缓存的身影。
Guava Cache是一个全内存的本地缓存实现,它提供了线程安全的实现机制。整体上来说Guava cache 是本地缓存的不二之选,简单易用,性能好。
Guava Cache有两种创建方式:
1. cacheLoader
2. callable callback
通过这两种方法创建的cache,和通常用map来缓存的做法比,不同在于,这两种方法都实现了一种逻辑——从缓存中取key X的值,如果该值已经缓存过了,则返回缓存中的值,如果没有缓存过,可以通过某个方法来获取这个值。但不同的在于cacheloader的定义比较宽泛,是针对整个cache定义的,可以认为是统一的根据key值load value的方法。而callable的方式较为灵活,允许你在get的时候指定。
-----------------------------------------------------
涉及线程安全, HashMap, 缓存等.
场景比如: Lazy initialization或Caching.
要对每一个需要用到的员工名字通过数据库查询她一年以来的加班多少, 绩效等等各种数据, 经过一系列步骤计算出员工的奖金数额.
这个东西算起来太费资源, 那么, 就要延迟计算, 只有员工主动跑来要加薪, 才去算. 而且要缓存, 如果这个员工对只发50元奖金心存不满, 反复来吵闹, 甚至投诉, 就不用重新计算.
自己写怎么写呢? 假设我那个算奖金的函数写好了, 就叫compute(name):
classBonusManager{privatestaticfinalMap<String,Integer> cache =newHashMap<String,Integer>();publicstaticInteger getBonus(String name){Integer bonus = cache.get(name);if(bonus ==null){ bonus = compute(name);// 危险在这里!多线程安全问题!} cache.put(name, bonus);return bonus;}privateInteger compute(String name){// 根据员工名字做一系列计算奖金的工作, 很费时}}
这里用HashMap实现了缓存, 但问题的关键是上面的代码不是线程安全的.
可以在getBonus()上面加一个synchronized关键字, 然后它就线程安全了. 但是,如果需要极高极高的并发能力呢?
可能会想到把HashMap改成ConcurrentHashMap.
近了一步了, 但是还不完全. 这是因为两个线程可能同时执行if (bonus == null)然后同时去运行compute(). 结果是, 可能对某些名字重复运行compute(). 还是不完美.
此时, 如果想到了双检查锁定机制(double-checked locking), 那就没招了.
但是, 单例模式请不要用double-checked locking, 相见这里.
遇到这种情况, 可以使用Google Guava中的LoadingCache, 之前的Guava版本利用MapMaker中的makeComputingMap()方法.
参考LoadingCache, CacheBuilder, CacheLoader类.
publicclassBonusManager{privatestaticLoadingCache<String,Integer> cache =CacheBuilder.newBuilder().maximumSize(1000)// 最多可以缓存1000个key.expireAfterWrite(10,TimeUnit.MINUTES)// 过期时间.build(newCacheLoader<String,Integer>(){publicInteger load(String key){return compute(key);// DOTO}});privatestaticInteger compute(String name){// 一些列复杂的计算过程}}
可把LoadingCache看作一个线程安全的Map.
使用时可以这样:
int bonus = cache.get("zhangsan");