目录

Life in Flow

知不知,尚矣;不知知,病矣。
不知不知,殆矣。

X

High Performance Cache

缓存作用

 缓存是在实际生产中非常常用的工具,用了缓存以后,我们可以避免重复计算,提高吞吐量。
 一个功能完备、性能强劲的缓存,需要考虑的点非常多。

第一阶段

  • 如果不使用 synchronized 多线程同时 put、扩容时候会出现线程安全隐患
  • 使用 synchronized 性能差,多线程需要串行化的方式执行 doCompute
  • 代码复用性差,代码侵入性强,违反开闭原则。
* 性能差:多个线程同时想计算的时候,需要慢慢等待(同步方法)
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 描述:     最简单的缓存形式:HashMap
 */
public class ImoocCache1 {
    //final 增加安全性,可读性
    private final HashMap<String,Integer> cache = new HashMap<>();

    public synchronized Integer computer(String userId) throws InterruptedException {
        Integer result = cache.get(userId);
        //先检查HashMap里面有没有保存过之前的计算结果
        if (result == null) {
            //如果缓存中找不到,那么需要现在计算一下结果,并且保存到HashMap中
            result = doCompute(userId);
            cache.put(userId, result);
        }
        return result;
    }

    private Integer doCompute(String userId) throws InterruptedException {
        TimeUnit.SECONDS.sleep(5);
        return new Integer(userId);
    }

    public static void main(String[] args) throws InterruptedException {
        ImoocCache1 imoocCache1 = new ImoocCache1();
        System.out.println("开始计算了");
        Integer result = imoocCache1.computer("13");
        System.out.println("第一次计算结果:"+result);
        result = imoocCache1.computer("13");
        System.out.println("第二次计算结果:"+result);

    }
}

第二阶段

  • 代码有重构空间——装饰者模式 (在已有方法的基础上,进行一定程度的装饰)
  • 假设 ExpensiveFunction 类是耗时计算的实现类,实现了 Computable 接口,但是其本身不具备缓存功能,也不需要考虑缓存的事情。
  • Computable 接口有一个计算函数 computer,用来代表耗时计算,每个计算器都要实现这个接口,这样就可以无侵入实现缓存功能

Computable 接口

/**
 * 描述:     有一个计算函数computer,用来代表耗时计算,每个计算器都要实现这个接口,这样就可以无侵入实现缓存功能
 */
public interface Computable <A,V>{

    V compute(A arg) throws Exception;
}

ExpensiveFunction 耗时计算类

/**
 * 描述:     耗时计算的实现类,实现了Computable接口,但是本身不具备缓存能力,不需要考虑缓存的事情
 */
public class ExpensiveFunction implements Computable<String, Integer>{

    @Override
    public Integer compute(String arg) throws Exception {
        Thread.sleep(5000);
        return Integer.valueOf(arg);
    }
}

ImoocCache2 装饰者类

/**
 * 描述:     用装饰者模式,给计算器自动添加缓存功能
 */
public class ImoocCache2<A,V> implements Computable<A,V> {

    private final Map<A, V> cache = new HashMap();

    //构造方法传入 Computable 接口的实现类的实例 ExpensiveFunction
    private  final Computable<A,V> c;

    public ImoocCache2(Computable<A, V> c) {
        this.c = c;
    }

    /**
     * 装饰过程 : 实现 Computable 接口,在实现方法中引入 调用 ExpensiveFunction 目标方法,基于此方法做装饰
     * @param arg
     * @return
     * @throws Exception
     */
    @Override
    public synchronized V compute(A arg) throws Exception {
        System.out.println("进入缓存机制");
        V result = cache.get(arg);
        if (result == null) {
            result = c.compute(arg); //目标方法织入
            cache.put(arg, result);
            return result;
        }
        System.out.println("命中缓存");
        return result;
    }

    public static void main(String[] args) throws Exception {
        //传入 ExpensiveFunction 实例
        ImoocCache2<String, Integer> expensiveComputer = new ImoocCache2<>(new ExpensiveFunction());
        Integer result = expensiveComputer.compute("666");
        System.out.println("第一次计算结果:"+result);
        result = expensiveComputer.compute("13");
        System.out.println("第二次计算结果:"+result);
        result = expensiveComputer.compute("13");
        System.out.println("第三次计算结果:"+result);
    }
}
进入缓存机制
第一次计算结果:666
进入缓存机制
第二次计算结果:13
进入缓存机制
命中缓存
第三次计算结果:13

缺点:

* 在多线程的环境下,已经计算过的 key,如果中间存在新 key,依然需要等待。
* 多线程的环境下,不同的Key需要等待执行,无法并行。
    public static void main(String[] args) throws Exception {
        ImoocCache3<String, Integer> expensiveComputer = new ImoocCache3<>(
                new ExpensiveFunction());
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Integer result = expensiveComputer.compute("666");
                    System.out.println("第一次的计算结果:"+result);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Integer result = expensiveComputer.compute("666");
                    System.out.println("第三次的计算结果:"+result);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Integer result = expensiveComputer.compute("667");
                    System.out.println("第二次的计算结果:"+result);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}
进入缓存机制
进入缓存机制
第一次的计算结果:666
进入缓存机制
第二次的计算结果:667
第三次的计算结果:666

第三阶段

 减小锁的粒度。
缺点:

* 如果在写的过程中读,同样存在线程不安全
* 如果读操作用synchronized 修饰,那么 读操作和读操作也是互斥的,性能低。
import com.xdclass.couponapp.test.imooccache.computable.Computable;
import com.xdclass.couponapp.test.imooccache.computable.ExpensiveFunction;
import java.util.HashMap;
import java.util.Map;

/**
 * 描述:     缩小了synchronized的粒度,提高性能,但是依然并发不安全
 */
public class ImoocCache4<A, V> implements Computable<A, V> {

    private final Map<A, V> cache = new HashMap();

    private final Computable<A, V> c;

    public ImoocCache4(Computable<A, V> c) {
        this.c = c;
    }

    @Override
    public V compute(A arg) throws Exception {
        System.out.println("进入缓存机制");
        V result = cache.get(arg);
        if (result == null) {
            result = c.compute(arg);
            synchronized (this) {
                cache.put(arg, result);
            }
        }
        return result;
    }

    public static void main(String[] args) throws Exception {
        ImoocCache4<String, Integer> expensiveComputer = new ImoocCache4<>(
                new ExpensiveFunction());
        Integer result = expensiveComputer.compute("666");
        System.out.println("第一次计算结果:" + result);
        result = expensiveComputer.compute("666");
        System.out.println("第二次计算结果:" + result);
    }
}

第四阶段

 虽然提高了并发效率(这里指的是进入目标方法的线程数),但并不意味着就是线程安全的,仅仅用 synchronized 方法修饰写操作是不够的。
 没有必要自己手动实现一个安全的 HashMap,即便是自己手动实现了也会因为各方面考虑不到存在性能和安全问题,不如直接使用现有的 ConcurrentHashMap 来进一步优化缓存。

缺点:

* 在计算完成之前,另一个要求计算相同值的请求到来,会导致计算两遍,这和缓存想避免多次计算的初衷背道而驰,是不可接受的。
* 如果有100个线程都请求同样的key 的计算,那么大量的重复计算会浪费大量的硬件资源。
    public static void main(String[] args) throws Exception {
        ImoocCache6<String, Integer> expensiveComputer = new ImoocCache6<>(
                new ExpensiveFunction());
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Integer result = expensiveComputer.compute("666");
                    System.out.println("第一次的计算结果:" + result);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Integer result = expensiveComputer.compute("666");
                    System.out.println("第三次的计算结果:" + result);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Integer result = expensiveComputer.compute("667");
                    System.out.println("第二次的计算结果:" + result);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
进入缓存机制
进入缓存机制
进入缓存机制
第一次的计算结果:666
第三次的计算结果:666
第二次的计算结果:667

第五阶段

 为了避免重复计算,Future、Callable 的妙用。前人种树后人乘凉。
 如果后请求的线程可以知道正在计算的线程的计算内容,就可以判断是不是已经存在重复计算,如果是存在于当前线程目标计算内容一样的线程,只需等待即可,就可以避免重复计算。
 Value 为 Future 并且立刻可以得到,ConcurrentHashMap 保证多线程之间的可见性
 因此当 ConcurrentHashMap 中已存在将要请求的 Key/Value,则会调用 Future.get() 方法阻塞,直到获取到线程结果。

缺点:(虽然概率比较小)

* 如果有两个同时计算 666 的线程,同时调用 cache.get() 方法,那么返回的结果都为 null,后面还是会创建两个任务去计算相同的值。
import com.xdclass.couponapp.test.imooccache.computable.Computable;
import com.xdclass.couponapp.test.imooccache.computable.ExpensiveFunction;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;

/**
 * 描述:     利用Future,避免重复计算
 */
public class ImoocCache7<A, V> implements Computable<A, V> {

    private final Map<A, Future<V>> cache = new ConcurrentHashMap<>();

    private final Computable<A, V> c;

    public ImoocCache7(Computable<A, V> c) {
        this.c = c;
    }

    /**
     * Value 为 Future 并且立刻可以得到,ConcurrentHashMap 保证多线程之间的可见性
     * 因此当 ConcurrentHashMap 中已存在将要请求的 Key/Value,则会调用 Future.get() 方法阻塞,直到获取到线程结果。
     * @param arg
     * @return
     * @throws Exception
     */
    @Override
    public V compute(A arg) throws Exception {
	//多个线程有可能会同时得到 null,存在安全隐患
        Future<V> f = cache.get(arg); 
        //如果是空:则有可能多个线程同时进入
        if (f == null) {
            //Callable
            Callable<V> callable = new Callable<V>() {
                @Override
                public V call() throws Exception {
                    return c.compute(arg);
                }
            };
            //FutureTask
            FutureTask<V> ft = new FutureTask<>(callable);
            //Future = FutureTask
            f = ft;
            //直接存入Future 作为缓存的 V
            cache.put(arg, ft);
            System.out.println("从FutureTask调用了计算函数");
            //开始计算
            ft.run();
        }
        //得到计算 Future 计算结果
        return f.get();
    }

    public static void main(String[] args) throws Exception {
        ImoocCache7<String, Integer> expensiveComputer = new ImoocCache7<>(
                new ExpensiveFunction());
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Integer result = expensiveComputer.compute("666");
                    System.out.println("第一次的计算结果:" + result);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Integer result = expensiveComputer.compute("667");
                    System.out.println("第三次的计算结果:" + result);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Integer result = expensiveComputer.compute("666");
                    System.out.println("第二次的计算结果:" + result);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
        
    }
}
从FutureTask调用了计算函数
从FutureTask调用了计算函数
第三次的计算结果:667
第二次的计算结果:666
第一次的计算结果:666

第六阶段

 原子化 cache.get() 方法。

import com.xdclass.couponapp.test.imooccache.computable.Computable;
import com.xdclass.couponapp.test.imooccache.computable.ExpensiveFunction;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;

/**
 * 描述:     利用Future,避免重复计算
 */
public class ImoocCache8<A, V> implements Computable<A, V> {

    private final Map<A, Future<V>> cache = new ConcurrentHashMap<>();

    private final Computable<A, V> c;

    public ImoocCache8(Computable<A, V> c) {
        this.c = c;
    }

    @Override
    public V compute(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.compute(arg);
                }
            };
            FutureTask<V> ft = new FutureTask<>(callable);

            // 如果集合中不存在就存放当前值,并且返回 原有位置的 value,所以第一存会返回 null
            // 如果已存就不会向容器存放当前的值,并且返回当前位置的值 ,所以会返回 Future
            f = cache.putIfAbsent(arg, ft);
            // 如果不存在就进行计算,否则返回当前key对应的Value(Future)
            if (f == null) {
                f = ft;
                System.out.println("从FutureTask调用了计算函数");
                //进行计算
                ft.run();
            }
        }
        //否则返回当前key对应的Value(Future)
        return f.get();
    }

    public static void main(String[] args) throws Exception {
        ImoocCache8<String, Integer> expensiveComputer = new ImoocCache8<>(
                new ExpensiveFunction());
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Integer result = expensiveComputer.compute("666");
                    System.out.println("第一次的计算结果:" + result);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Integer result = expensiveComputer.compute("666");
                    System.out.println("第三次的计算结果:" + result);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Integer result = expensiveComputer.compute("667");
                    System.out.println("第二次的计算结果:" + result);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}
从FutureTask调用了计算函数
从FutureTask调用了计算函数
第二次的计算结果:667
第三次的计算结果:666
第一次的计算结果:666

任务异常处理

 3 种异常之所以用不同的 catch 块捕获,是因为它们的处理逻辑是不同的。

* CancellationException、InterruptedException 是人为取消的,那么应该立即终止任务
* 但是如果是计算错误,并且明确知道多试几次就可以得到正确结果,那么逻辑应该是重试,而不应该是抛出异常。
* while(true) 来保证计算出错不会影响逻辑,会进入下次循环(重试),如果是人为取消或中断,那么就立即停止并且抛出异常。

 cache.remove(arg); 解决缓存污染问题。

耗时计算的实现类(有概率计算失败)

/**
 * 描述:     耗时计算的实现类,有概率计算失败
 */
public class MayFail implements Computable<String, Integer>{

    @Override
    public Integer compute(String arg) throws Exception {
        double random = Math.random();
	//模拟50%的情况可能会计算失败,抛出异常
        if (random > 0.5) {
            throw new IOException("读取文件出错");
        }
        Thread.sleep(3000);
        return Integer.valueOf(arg);
    }
}

Cache 类

/**
 * 描述:     利用Future,避免重复计算
 */
public class ImoocCache9<A, V> implements Computable<A, V> {

    private final Map<A, Future<V>> cache = new ConcurrentHashMap<>();

    private final Computable<A, V> c;

    public ImoocCache9(Computable<A, V> c) {
        this.c = c;
    }

    @Override
    public V compute(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.compute(arg);
                    }
                };
                FutureTask<V> ft = new FutureTask<>(callable);
                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) {
                //从缓存中删除(缓存污染)
                cache.remove(arg);
                throw e;
            } catch (ExecutionException e) {
                System.out.println("计算错误,需要重试");
                //从缓存中删除(缓存污染)
                cache.remove(arg);
            }
        }
    }

    public static void main(String[] args) throws Exception {
        //实例改为 MayFail
        ImoocCache9<String, Integer> expensiveComputer = new ImoocCache9<>(new MayFail());
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Integer result = expensiveComputer.compute("666");
                    System.out.println("第一次的计算结果:" + result);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Integer result = expensiveComputer.compute("666");
                    System.out.println("第三次的计算结果:" + result);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Integer result = expensiveComputer.compute("667");
                    System.out.println("第二次的计算结果:" + result);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();

        //模拟中断
        //Future<Integer> future = expensiveComputer.cache.get("666");
        //future.cancel(true);
    }
}
######################### 模拟中断
从FutureTask调用了计算函数
被取消了
从FutureTask调用了计算函数
被取消了
java.util.concurrent.CancellationException
	at java.util.concurrent.FutureTask.report(FutureTask.java:121)
	at java.util.concurrent.FutureTask.get(FutureTask.java:192)
	at com.xdclass.couponapp.test.imooccache.ImoocCache9.compute(ImoocCache9.java:48)
	at com.xdclass.couponapp.test.imooccache.ImoocCache9$3.run(ImoocCache9.java:84)
	at java.lang.Thread.run(Thread.java:748)
java.util.concurrent.CancellationException
	at java.util.concurrent.FutureTask.report(FutureTask.java:121)
	at java.util.concurrent.FutureTask.get(FutureTask.java:192)
	at com.xdclass.couponapp.test.imooccache.ImoocCache9.compute(ImoocCache9.java:48)
	at com.xdclass.couponapp.test.imooccache.ImoocCache9$2.run(ImoocCache9.java:73)
	at java.lang.Thread.run(Thread.java:748)
第二次的计算结果:667

######################### 增加循环重试
从FutureTask调用了计算函数
从FutureTask调用了计算函数
计算错误,需要重试
计算错误,需要重试
计算错误,需要重试
从FutureTask调用了计算函数
从FutureTask调用了计算函数
从FutureTask调用了计算函数
计算错误,需要重试
计算错误,需要重试
从FutureTask调用了计算函数
从FutureTask调用了计算函数
计算错误,需要重试
从FutureTask调用了计算函数
计算错误,需要重试
从FutureTask调用了计算函数
计算错误,需要重试
从FutureTask调用了计算函数
第二次的计算结果:667
第三次的计算结果:666
第一次的计算结果:666

缓存过期

 出于安全性考虑,缓存需要设置有效期,到期自动失效,否则如果缓存一直不失效,那么带来缓存不一致等问题
 缓存定时删除的能力。

/**
 * 描述:     出于安全性考虑,缓存需要设置有效期,到期自动失效,否则如果缓存一直不失效,那么带来缓存不一致等问题
 */
public class ImoocCache10<A, V> implements Computable<A, V> {

    private final Map<A, Future<V>> cache = new ConcurrentHashMap<>();

    private final Computable<A, V> c;

    //周期任务的线程池
    public final static ScheduledExecutorService executor = Executors.newScheduledThreadPool(5);

    public ImoocCache10(Computable<A, V> c) {
        this.c = c;
    }

    @Override
    public V compute(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.compute(arg);
                    }
                };
                FutureTask<V> ft = new FutureTask<>(callable);
                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) {
                cache.remove(arg);
                throw e;
            } catch (ExecutionException e) {
                System.out.println("计算错误,需要重试");
                cache.remove(arg);
            }
        }
    }

    /**
     * 方法重载 compute()
     * @param arg
     * @param expire
     * @return
     * @throws ExecutionException
     * @throws InterruptedException
     */
    public V compute(A arg, long expire) throws ExecutionException, InterruptedException {
        if (expire>0) {
            executor.schedule(new Runnable() {
                @Override
                public void run() {
                    expire(arg);
                }
            }, expire, TimeUnit.MILLISECONDS);
        }
        return compute(arg);
    }

    /**
     * 清除过期缓存的方法
     * @param key
     */
    public synchronized void expire(A key) {
        //检查缓存中是否还存在对应的key
        Future<V> future = cache.get(key);
        //如果缓存中存在对应的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 {
        ImoocCache10<String, Integer> expensiveComputer = new ImoocCache10<>(
                new MayFail());
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Integer result = expensiveComputer.compute("666",5000L);
                    System.out.println("第一次的计算结果:" + result);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Integer result = expensiveComputer.compute("666");
                    System.out.println("第三次的计算结果:" + result);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Integer result = expensiveComputer.compute("667");
                    System.out.println("第二次的计算结果:" + result);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();


        Thread.sleep(6000L); //已经过期(缓存设置的是 5000L)
        Integer result = expensiveComputer.compute("666");
        System.out.println("主线程的计算结果:" + result);

        executor.shutdown();
    }
}
从FutureTask调用了计算函数
从FutureTask调用了计算函数
第二次的计算结果:667
第三次的计算结果:666
第一次的计算结果:666
过期时间到,缓存被清除
从FutureTask调用了计算函数	--- 这里可以看出 '666' 的缓存已失效
主线程的计算结果:666

高并发访问

 1~10000 同时计算,同时设置过期,在同一时间就会出现大量缓存过期,造成缓存雪崩、缓存击穿等高并发下的问题。
 为了解决缓存集中过期的问题,可以将缓存过期时间设为随机。
 缓存方法改为调用 computeRandomExpire(A arg) 方法即可。

    /**
     * 缓存随机过期时间
     * @param arg
     * @return
     * @throws ExecutionException
     * @throws InterruptedException
     */
    public V computeRandomExpire(A arg) throws ExecutionException, InterruptedException {
        long randomExpire = (long) (Math.random() * 10000);
        //调用  compute() 方法重载
        return compute(arg, randomExpire);
    }

模拟大量请求(观测缓存效果)

 集中大量线程在同一时间访问缓存。

* 利用 CountDownLatch 让大量线程集中,在某一个时间同时达到。
* 让每个线程都打印准备计算时的时间,并且利用 ThreadLocal 

import com.test.imooccache.computable.ExpensiveFunction;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 描述:     TODO
 */
public class ImoocCache12 {

    static ImoocCache10<String, Integer> expensiveComputer = new ImoocCache10<>(
            new ExpensiveFunction());

    //压力集中:
    public static CountDownLatch countDownLatch = new CountDownLatch(1);

    public static void main(String[] args) throws InterruptedException {
        ExecutorService service = Executors.newFixedThreadPool(100);

        //统计时间
        long start = System.currentTimeMillis();

        for (int i = 0; i < 100; i++) {
            service.submit(() -> {
                Integer result = null;
                try {
                    //等待
                    System.out.println(Thread.currentThread().getName()+"开始等待");
                    countDownLatch.await();

                    //放行
                    //获取 用于初始化 SimpleDateFormat
                    SimpleDateFormat dateFormat = ThreadSafeFormatter.dateFormatter.get();
                    //利用 SimpleDateFormat 格式化当前时间
                    String time = dateFormat.format(new Date());
                    //打印当前时间
                    System.out.println(Thread.currentThread().getName()+"   "+time+"被放行");
                    //调用计算函数
                    result = expensiveComputer.compute("666");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
                System.out.println(result);
            });
        }

        Thread.sleep(5000);
        countDownLatch.countDown();
        service.shutdown();

/*        while (!service.isTerminated()) {
            System.out.println("总耗时:" + (System.currentTimeMillis() - start));
        }*/
    }
}

//避免每个线程都创建 SimpleDateFormat ,线程安全的
class ThreadSafeFormatter {

    public static ThreadLocal<SimpleDateFormat> dateFormatter = new ThreadLocal<SimpleDateFormat>() {

        //每个线程会调用本方法一次,用于初始化 SimpleDateFormat
        @Override
        protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat("mm:ss");
        }

        //首次调用本方法时,会调用initialValue();后面的调用会返回第一次创建的值
        @Override
        public SimpleDateFormat get() {
            return super.get();
        }
    };
}
pool-2-thread-3开始等待
...
pool-2-thread-99开始等待
pool-2-thread-100开始等待
pool-2-thread-7   21:07被放行
...
从FutureTask调用了计算函数		//只有一个线程还调用计算函数
pool-2-thread-1   21:07被放行
pool-2-thread-47   21:07被放行
pool-2-thread-40   21:07被放行
pool-2-thread-88   21:07被放行
pool-2-thread-100   21:07被放行
pool-2-thread-80   21:07被放行
pool-2-thread-54   21:07被放行
...
666
666
666

作者:Soulboy