缓存框架
1、使用HashMap实现一个缓存
/** * @Classname ImocCache1 * @Description 最简单的缓存形式,HashMap * @Date 2021/5/24 22:44 * @Created by WangXiong */ public class ImocCache1 { private HashMap<String, Integer> cache = new HashMap<>(); public Integer computer(String userId) throws InterruptedException { Integer result = cache.get(userId); //先检查hashmap中是否有这个缓存 if (result == null){ //如果缓存中找不到,需要重新计算一下结果,然后保存到cache中 result = doCompute(userId); cache.put(userId, result); } return result; } public Integer doCompute(String userId) throws InterruptedException { TimeUnit.SECONDS.sleep(5); return new Integer(userId); } public static void main(String[] args) throws InterruptedException { ImocCache1 imocCache1 = new ImocCache1(); System.out.println("开始计算了"); Integer result = imocCache1.computer("13"); System.out.println("第一次计算的结果"+result); result = imocCache1.computer("13"); System.out.println("第二次计算的结果"+result); } }
多个线程同时进来,会有并发安全问题,且代码耦合
2、用装饰者模式来解决解耦的问题
/** * @Classname Computable * @Description 有一个计算函数computer,用来代表耗时计算,每个计算器都要实现这个接口,这样就 * 可以无侵入实现缓存的功能 * @Date 2021/5/24 22:56 * @Created by WangXiong */ public interface Computable<A, V> { V computer(A arg) throws Exception; }
/** * @Classname ExpensiveFuntion * @Description 耗时计算的实现类,实现了Computable接口,但是本身不具备缓存能力,不需要考虑 * 缓存的事情 * @Date 2021/5/24 22:58 * @Created by WangXiong */ public class ExpensiveFunction implements Computable<String, Integer>{ @Override public Integer computer(String arg) throws Exception { Thread.sleep(5000); return Integer.valueOf(arg); } }
/** * @Classname ImocCache1 * @Description 用装饰者模式,给计算器自动添加缓存功能 * @Date 2021/5/24 22:44 * @Created by WangXiong */ public class ImocCache2<A, V> implements Computable<A, V> { private HashMap<A, V> cache = new HashMap<>(); private final Computable<A, V> c; public ImocCache2(Computable<A, V> c) { this.c = c; } @Override public V computer(A arg) throws Exception { System.out.println("进入缓存机制"); V result = cache.get(arg); if (result == null){ result = c.computer(arg); cache.put(arg, result); } return result; } public static void main(String[] args) throws Exception { ImocCache2<String, Integer> exImocCache2 = new ImocCache2<>(new ExpensiveFunction()); Integer result = exImocCache2.computer("666"); System.out.println("第一次计算结果"+result); result = exImocCache2.computer("666"); System.out.println("第二次计算结果"+result); } }
3、多线程中会有线程安全问题
使用synchronize来处理线程安全问题
/** * @Classname ImocCache1 * @Description 演示synchronized的问题,导致性能差,没有办法多个线程同时访问缓存 * @Date 2021/5/24 22:44 * @Created by WangXiong */ public class ImocCache3<A, V> implements Computable<A, V> { private HashMap<A, V> cache = new HashMap<>(); private final Computable<A, V> c; public ImocCache3(Computable<A, V> c) { this.c = c; } @Override //由于加上了synchronized,这个方法同时只能一个线程访问,效率低下 public synchronized V computer(A arg) throws Exception { System.out.println("进入缓存机制1"); V result = cache.get(arg); if (result == null){ result = c.computer(arg); cache.put(arg, result); } return result; } public static void main(String[] args) throws Exception { ImocCache3<String, Integer> exImocCache2 = new ImocCache3<>(new ExpensiveFunction()); new Thread(new Runnable() { @Override public void run() { try { Integer result = exImocCache2.computer("666"); System.out.println("第一个线程end"+result); } catch (Exception e) { e.printStackTrace(); } } }).start(); new Thread(new Runnable() { @Override public void run() { try { Integer result = exImocCache2.computer("666"); System.out.println("第二个线程end"+result); } catch (Exception e) { e.printStackTrace(); } } }).start(); new Thread(new Runnable() { @Override public void run() { try { Integer result = exImocCache2.computer("777"); System.out.println("第三个线程end"+result); } catch (Exception e) { e.printStackTrace(); } } }).start(); } }
4、降低synchronize粒度
将synchronize的粒度降低,让线程性能提高,但是这样依旧会有性能问题。
/** * @Classname ImocCache1 * @Description 使用synchronized缩小粒度,提高性能,但是依旧是线程不安全的 * @Date 2021/5/24 22:44 * @Created by WangXiong */ public class ImocCache4<A, V> implements Computable<A, V> { private HashMap<A, V> cache = new HashMap<>(); private final Computable<A, V> c; public ImocCache4(Computable<A, V> c) { this.c = c; } @Override public V computer(A arg) throws Exception { System.out.println("进入缓存机制1"); //读的时候也会有线程安全的问题 V result = cache.get(arg); if (result == null){ result = c.computer(arg); synchronized (this) { cache.put(arg, result); } } return result; } public static void main(String[] args) throws Exception { ImocCache4<String, Integer> exImocCache2 = new ImocCache4<>(new ExpensiveFunction()); new Thread(new Runnable() { @Override public void run() { try { Integer result = exImocCache2.computer("666"); System.out.println("第一个线程end"+result); } catch (Exception e) { e.printStackTrace(); } } }).start(); new Thread(new Runnable() { @Override public void run() { try { Integer result = exImocCache2.computer("666"); System.out.println("第二个线程end"+result); } catch (Exception e) { e.printStackTrace(); } } }).start(); new Thread(new Runnable() { @Override public void run() { try { Integer result = exImocCache2.computer("777"); System.out.println("第三个线程end"+result); } catch (Exception e) { e.printStackTrace(); } } }).start(); } }
5.使用线程安全的HashMap
/** * @Classname ImocCache1 * @Description 使用ConcurrentHashMap优化缓存 * @Date 2021/5/24 22:44 * @Created by WangXiong */ public class ImocCache5<A, V> implements Computable<A, V> { private ConcurrentHashMap<A, V> cache = new ConcurrentHashMap<>(); private final Computable<A, V> c; public ImocCache5(Computable<A, V> c) { this.c = c; } @Override public V computer(A arg) throws Exception { System.out.println("进入缓存机制1"); //读的时候也会有线程安全的问题 V result = cache.get(arg); if (result == null){ result = c.computer(arg); cache.put(arg, result); } return result; } public static void main(String[] args) throws Exception { ImocCache5<String, Integer> exImocCache2 = new ImocCache5<>(new ExpensiveFunction()); new Thread(new Runnable() { @Override public void run() { try { Integer result = exImocCache2.computer("666"); System.out.println("第一个线程end"+result); } catch (Exception e) { e.printStackTrace(); } } }).start(); new Thread(new Runnable() { @Override public void run() { try { Integer result = exImocCache2.computer("666"); System.out.println("第二个线程end"+result); } catch (Exception e) { e.printStackTrace(); } } }).start(); new Thread(new Runnable() { @Override public void run() { try { Integer result = exImocCache2.computer("777"); System.out.println("第三个线程end"+result); } catch (Exception e) { e.printStackTrace(); } } }).start(); } }
6、解决缓存值重复计算的问题
演示值计算多遍的问题
/** * @Classname ImocCache1 * @Description 使用ConcurrentHashMap优化缓存,还是会有问题,会有计算多遍的问题 * @Date 2021/5/24 22:44 * @Created by WangXiong */ public class ImocCache6<A, V> implements Computable<A, V> { private ConcurrentHashMap<A, V> cache = new ConcurrentHashMap<>(); private final Computable<A, V> c; public ImocCache6(Computable<A, V> c) { this.c = c; } @Override public V computer(A arg) throws Exception { System.out.println("进入缓存机制1"); V result = cache.get(arg); if (result == null){ //计算是耗时的,第二个线程过来还没有算好,也会去进行计算 result = c.computer(arg); cache.put(arg, result); } return result; } public static void main(String[] args) throws Exception { ImocCache6<String, Integer> exImocCache2 = new ImocCache6<>(new ExpensiveFunction()); new Thread(new Runnable() { @Override public void run() { try { Integer result = exImocCache2.computer("666"); System.out.println("第一个线程end"+result); } catch (Exception e) { e.printStackTrace(); } } }).start(); new Thread(new Runnable() { @Override public void run() { try { Integer result = exImocCache2.computer("666"); System.out.println("第二个线程end"+result); } catch (Exception e) { e.printStackTrace(); } } }).start(); new Thread(new Runnable() { @Override public void run() { try { Integer result = exImocCache2.computer("777"); System.out.println("第三个线程end"+result); } catch (Exception e) { e.printStackTrace(); } } }).start(); } }
效果
使用Future来解决重复计算的问题
/** * @Classname ImocCache1 * @Description 使用Future来解决重复计算的问题 * @Date 2021/5/24 22:44 * @Created by WangXiong */ public class ImocCache7<A, V> implements Computable<A, V> { private final Map<A, Future<V>> cache = new ConcurrentHashMap<>(); private final Computable<A, V> c; public ImocCache7(Computable<A, V> c) { this.c = c; } @Override public V computer(A arg) throws Exception { Future<V> f = cache.get(arg); if (f == null) { Callable<V> callable = new Callable<V>() { @Override public V call() throws Exception { return c.computer(arg); } }; FutureTask<V> ft = new FutureTask<>(callable); f = ft; cache.put(arg, ft); System.out.println("从FutureTask调用了计算函数"); ft.run(); } return f.get(); } public static void main(String[] args) throws Exception { ImocCache7<String, Integer> exImocCache2 = new ImocCache7<>(new ExpensiveFunction()); new Thread(new Runnable() { @Override public void run() { try { Integer result = exImocCache2.computer("666"); System.out.println("第一个线程end"+result); } catch (Exception e) { e.printStackTrace(); } } }).start(); new Thread(new Runnable() { @Override public void run() { try { Integer result = exImocCache2.computer("777"); System.out.println("第三个线程end"+result); } catch (Exception e) { e.printStackTrace(); } } }).start(); new Thread(new Runnable() { @Override public void run() { try { Integer result = exImocCache2.computer("666"); System.out.println("第二个线程end"+result); } catch (Exception e) { e.printStackTrace(); } } }).start(); } }
效果
我们调整下线程之间的顺序,依旧会出现重复计算的问题。因为在第一个线程还没有put进去的时候,第二个线程就去get了,这个时候也会认为是没有计算的,一旦进入到if中,就一定回去进行计算。可以使用ConcurrentHashMap提供的方法来解决这个问题,因为ConcurrentHashMap的方法都是线程安全的,所以不用考虑线程安全问题
/** * @Classname ImocCache1 * @Description 使用Future依旧会出现一定几率的重复计算, * 使用ConcurrentHashMap的提供的方法putIfAbsent()进行避免 * @Date 2021/5/24 22:44 * @Created by WangXiong */ public class ImocCache8<A, V> implements Computable<A, V> { private final Map<A, Future<V>> cache = new ConcurrentHashMap<>(); private final Computable<A, V> c; public ImocCache8(Computable<A, V> c) { this.c = c; } @Override public V computer(A arg) throws Exception { Future<V> f = cache.get(arg); if (f == null) { Callable<V> callable = new Callable<V>() { @Override public V call() throws Exception { return c.computer(arg); } }; FutureTask<V> ft = new FutureTask<>(callable); //putIfAbsentr如果存在arg的key的数据,就不会进行put操作,如果为空,会返回null f = cache.putIfAbsent(arg, ft); if (f == null) { f = ft; System.out.println("从FutureTask调用了计算函数"); ft.run(); } } return f.get(); } public static void main(String[] args) throws Exception { ImocCache8<String, Integer> exImocCache2 = new ImocCache8<>(new ExpensiveFunction()); new Thread(new Runnable() { @Override public void run() { try { Integer result = exImocCache2.computer("666"); System.out.println("第一个线程end"+result); } catch (Exception e) { e.printStackTrace(); } } }).start(); new Thread(new Runnable() { @Override public void run() { try { Integer result = exImocCache2.computer("666"); System.out.println("第二个线程end"+result); } catch (Exception e) { e.printStackTrace(); } } }).start(); new Thread(new Runnable() { @Override public void run() { try { Integer result = exImocCache2.computer("777"); System.out.println("第三个线程end"+result); } catch (Exception e) { e.printStackTrace(); } } }).start(); } }
7、给缓存类增加异常处理的能力
/** * @Classname ImocCache1 * @Description 给缓存类增加异常处理的能力 * @Date 2021/5/24 22:44 * @Created by WangXiong */ public class ImocCache9<A, V> implements Computable<A, V> { private final Map<A, Future<V>> cache = new ConcurrentHashMap<>(); private final Computable<A, V> c; public ImocCache9(Computable<A, V> c) { this.c = c; } @Override public V computer(A arg) throws InterruptedException, ExecutionException { while (true) { Future<V> f = cache.get(arg); if (f == null) { Callable<V> callable = new Callable<V>() { @Override public V call() throws Exception { return c.computer(arg); } }; FutureTask<V> ft = new FutureTask<>(callable); //putIfAbsentr如果存在arg的key的数据,就不会进行put操作,如果为空,会返回null f = cache.putIfAbsent(arg, ft); if (f == null) { f = ft; System.out.println("从FutureTask调用了计算函数"); ft.run(); } } try { return f.get(); } catch (CancellationException e) { System.out.println("被取消了"); cache.remove(arg); throw e; } catch (InterruptedException e) { System.out.println("被中断了"); cache.remove(arg); throw e; } catch (ExecutionException e) { System.out.println("计算错误,需要重试"); //异常了,一定要删除缓存数据,不然会有污染数据,导致一直重试 cache.remove(arg); } } } public static void main(String[] args) throws Exception { ImocCache9<String, Integer> exImocCache2 = new ImocCache9<>(new MayFail()); new Thread(new Runnable() { @Override public void run() { try { Integer result = exImocCache2.computer("666"); System.out.println("第一个线程end"+result); } catch (Exception e) { e.printStackTrace(); } } }).start(); new Thread(new Runnable() { @Override public void run() { try { Integer result = exImocCache2.computer("666"); System.out.println("第二个线程end"+result); } catch (Exception e) { e.printStackTrace(); } } }).start(); new Thread(new Runnable() { @Override public void run() { try { Integer result = exImocCache2.computer("777"); System.out.println("第三个线程end"+result); } catch (Exception e) { e.printStackTrace(); } } }).start(); // Thread.sleep(3000); // Future<Integer> future = exImocCache2.cache.get("666"); // future.cancel(false); } }
8、 给缓存类增加过期功能
/** * @Classname ImocCache1 * @Description 给缓存类增加缓存过期的功能 * @Date 2021/5/24 22:44 * @Created by WangXiong */ public class ImocCache10<A, V> implements Computable<A, V> { private final Map<A, Future<V>> cache = new ConcurrentHashMap<>(); private final Computable<A, V> c; public ImocCache10(Computable<A, V> c) { this.c = c; } @Override public V computer(A arg) throws InterruptedException, ExecutionException { while (true) { Future<V> f = cache.get(arg); if (f == null) { Callable<V> callable = new Callable<V>() { @Override public V call() throws Exception { return c.computer(arg); } }; FutureTask<V> ft = new FutureTask<>(callable); //putIfAbsentr如果存在arg的key的数据,就不会进行put操作,如果为空,会返回null f = cache.putIfAbsent(arg, ft); if (f == null) { f = ft; System.out.println("从FutureTask调用了计算函数"); ft.run(); } } try { return f.get(); } catch (CancellationException e) { System.out.println("被取消了"); cache.remove(arg); throw e; } catch (InterruptedException e) { System.out.println("被中断了"); cache.remove(arg); throw e; } catch (ExecutionException e) { System.out.println("计算错误,需要重试"); //异常了,一定要删除缓存数据,不然会有污染数据,导致一直重试 cache.remove(arg); } } } public final ScheduledExecutorService service = Executors.newScheduledThreadPool(5); //为避免缓存雪崩,写一个产生随机值的缓存时间的方法 public V computerRandomExpire(A arg) throws ExecutionException, InterruptedException { long randomExpire = (long) (Math.random() * 10000); System.out.println("computerRandomExpire设置的缓存时间:"+randomExpire); return computer(arg, randomExpire); } public V computer(A arg, long expire) throws ExecutionException, InterruptedException { //设置相同的缓存失效时间,会造成缓存雪崩 service.schedule(new Runnable() { @Override public void run() { expire(arg); } }, expire, TimeUnit.MILLISECONDS); return computer(arg); } public synchronized void expire(A key){ Future<V> future = cache.get(key); if (future != null) { if (!future.isDone()){ System.out.println("Future任务被取消"); future.cancel(true); } System.out.println("过期时间到,缓存被清除"); cache.remove(key); } } public static void main(String[] args) throws Exception { ImocCache10<String, Integer> exImocCache2 = new ImocCache10<>(new MayFail()); new Thread(new Runnable() { @Override public void run() { try { Integer result = exImocCache2.computerRandomExpire("666"); System.out.println("第一个线程end"+result); } catch (Exception e) { e.printStackTrace(); } } }).start(); new Thread(new Runnable() { @Override public void run() { try { Integer result = exImocCache2.computer("666"); System.out.println("第二个线程end"+result); } catch (Exception e) { e.printStackTrace(); } } }).start(); new Thread(new Runnable() { @Override public void run() { try { Integer result = exImocCache2.computer("777"); System.out.println("第三个线程end"+result); } catch (Exception e) { e.printStackTrace(); } } }).start(); // Thread.sleep(3000); // Future<Integer> future = exImocCache2.cache.get("666"); // future.cancel(false); Thread.sleep(7000l); new Thread(new Runnable() { @Override public void run() { try { Integer result = exImocCache2.computer("666"); System.out.println("第四个线程end"+result); } catch (Exception e) { e.printStackTrace(); } } }).start(); } }
9、 测试缓存性能
/** * @Classname ImocCache1 * @Description 测试环境耗时 * @Date 2021/5/24 22:44 * @Created by WangXiong */ public class ImocCache11<A, V> { static ImocCache10<String, Integer> imocCache10 = new ImocCache10<>(new ExpensiveFunction()); public static void main(String[] args) { ExecutorService service = Executors.newFixedThreadPool(3000); long start = System.currentTimeMillis(); for (int i = 0; i < 3000; i++) { service.submit(()->{ try { imocCache10.computer("666"); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } }); } service.shutdown(); while (!service.isTerminated()){ } long end = System.currentTimeMillis(); System.out.println("耗时:"+(end-start)); } }
借助CountDownLatch来实现压测的效果
/** * @Classname ImocCache1 * @Description 测试环境耗时 * @Date 2021/5/24 22:44 * @Created by WangXiong */ public class ImocCache11<A, V> { static ImocCache10<String, Integer> imocCache10 = new ImocCache10<>(new ExpensiveFunction()); public static final CountDownLatch countDownLatch = new CountDownLatch(1); public static void main(String[] args) throws InterruptedException { ExecutorService service = Executors.newFixedThreadPool(3000); long start = System.currentTimeMillis(); for (int i = 0; i < 3000; i++) { service.submit(()->{ try { System.out.println(Thread.currentThread().getName() + "开始等待"); countDownLatch.await(); Integer result = imocCache10.computer("666"); System.out.println(Thread.currentThread().getName() + "结束等待"); System.out.println(result); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } }); } Thread.sleep(5000); countDownLatch.countDown(); service.shutdown(); } }
每个线程都打印自己的时间,用ThreadLocal来实现
/** * @Classname ImocCache1 * @Description 测试环境耗时,使用ThreadLocal线程来存储变量 * @Date 2021/5/24 22:44 * @Created by WangXiong */ public class ImocCache11<A, V> { static ImocCache10<String, Integer> imocCache10 = new ImocCache10<>(new ExpensiveFunction()); public static final CountDownLatch countDownLatch = new CountDownLatch(1); public static void main(String[] args) throws InterruptedException { ExecutorService service = Executors.newFixedThreadPool(3000); long start = System.currentTimeMillis(); for (int i = 0; i < 3000; i++) { service.submit(()->{ try { System.out.println(Thread.currentThread().getName() + "开始等待"); countDownLatch.await(); SimpleDateFormat dateFormat = ThreadSafeFormatter.dft.get(); String format = dateFormat.format(new Date()); Integer result = imocCache10.computer("666"); System.out.println(Thread.currentThread().getName() + " " + format + "被放行"); // System.out.println(result); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } }); } Thread.sleep(5000); countDownLatch.countDown(); service.shutdown(); } } class ThreadSafeFormatter { public static ThreadLocal<SimpleDateFormat> dft = new ThreadLocal<SimpleDateFormat>(){ @Override protected SimpleDateFormat initialValue() { return new SimpleDateFormat("mm:ss"); } @Override public SimpleDateFormat get() { return super.get(); } }; }
10、总结
用HashMap作为缓存进行迭代
用synchronize来解决线程安全问题,但是会影响性能
用装饰者模式来解决代码侵入的问题,重复的代码可以进行复用
将类锁的synchronize换为粒度更小的代码块,但是依旧会有线程安全问题
使用ConcurrentHashMap线程安全的map来解决,但是在一定的情况下依旧是线程不安全,并且会有重复计算的问题
使用Future来解决重复计算的问题,并且用ConcurrentHashMap提供的线程安全的方法来解决上面一定情况下线程不安全的情况
增加异常处理,对取消和中断做不同的处理,对计算错误可以进行重试(需要在计算失败的时候,进行缓存移除,不然会有缓存污染问题)
需要增加缓存过期问题,对缓存时间设置的不一致,设置随机值 ,避免雪崩
利用CountDownLatch来进行压测
使用ThreadLocal来处理打印时间的问题