java併發編程實戰5.6 構建高效且可伸縮的結果緩存

計算接口:

/**
 * @author yaoqiang
 * @create 2020-03-26 20:38
 * @desc 模擬計算類型
 **/
public interface Computable<A,V> {
    V compute(A a) throws InterruptedException;
}

計算實現:

/**
 * @author yaoqiang
 * @create 2020-03-26 20:40
 * @desc 計算實例
 **/
public class ExpensiveFunction implements Computable<String, BigInteger> {

    @Override
    public BigInteger compute(String s) throws InterruptedException {
        TimeUnit.SECONDS.sleep(1);
        return new BigInteger(s);
    }
}

初步緩存實現:  

直接給計算方法上面加鎖,這樣可以實現緩存的目的,但是計算變成了同步方法,只能由一個線程能夠計算,容易造成大量阻塞,有的時候甚至比不上不用緩存,這種方式過於保守

/**
 * @author yaoqiang
 * @create 2020-03-26 20:42
 * @desc 記憶
 **/
public class Memorizer<A,V> implements Computable<A,V> {

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

    private final Computable<A,V> computable;

    public Memorizer(Computable<A, V> computable) {
        this.computable = computable;
    }

    @Override
    public synchronized V compute(A a) throws InterruptedException {
        V v = cache.get(a);
        if(v == null){
            V result = computable.compute(a);
            cache.put(a, result);
        }
        return v;
    }
}

初步升級:

使用ConcurrentHashMap,具有更好的併發性能,但是存在兩個缺點

1. 如果計算需要花費大量時間,第一個線程正在計算,而第二個線程準備計算同樣的值,但是發現緩存裏面沒有,於是又要重複計算

2. 併發下的問題,如果兩個線程,同時返回null,同樣重複計算,重複添加,造成迷惑的結果

/**
 * @author yaoqiang
 * @create 2020-03-26 20:42
 * @desc 記憶
 **/
public class Memorizer<A,V> implements Computable<A,V> {

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

    private final Computable<A,V> computable;

    public Memorizer(Computable<A, V> computable) {
        this.computable = computable;
    }

    @Override
    public V compute(A a) throws InterruptedException {
        V v = cache.get(a);
        if(v == null){
            V result = computable.compute(a);
            cache.put(a, result);
        }
        return v;
    }
}

解決問題1:

放入一個準備future,放入map對象,如果有重複,通過future來獲取,

FutureTask有幾種狀態:等待運行,正在運行,運行完成,只要有狀態了,就不會在出現,因爲運算是按過長而產生的重複計算

/**
 * @author yaoqiang
 * @create 2020-03-26 20:42
 * @desc 記憶
 **/
public class Memorizer<A,V> implements Computable<A,V> {

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

    private final Computable<A,V> computable;

    public Memorizer(Computable<A, V> computable) {
        this.computable = computable;
    }

    @Override
    public V compute(A a) throws InterruptedException, ExecutionException {
        Future<V> v = cache.get(a);
        if(v == null){
            Callable<V> callable = ()-> computable.compute(a);
            FutureTask<V> futureTask = new FutureTask<>(callable);
            v = futureTask;
            cache.put(a,v);
            futureTask.run();
        }
        return v.get();

    }
}

解決問題2:

再次判斷,使用

putIfAbsent 來判斷是否需要運算,避免多線程導致的重複運算,清理很重要,這也式爲了保證緩存的準確性
/**
 * @author yaoqiang
 * @create 2020-03-26 20:42
 * @desc 記憶
 **/
public class Memorizer<A,V> implements Computable<A,V> {

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

    private final Computable<A,V> computable;

    public Memorizer(Computable<A, V> computable) {
        this.computable = computable;
    }

    @Override
    public V compute(A a) throws InterruptedException {
        while (true) {
            Future<V> v = cache.get(a);
            if (v == null) {
                Callable<V> callable = () -> computable.compute(a);
                FutureTask<V> futureTask = new FutureTask<>(callable);
                v = cache.putIfAbsent(a, futureTask);
                if (v == null) {
                    v = futureTask;
                    futureTask.run();
                }
            }
            try {
                return v.get();
            } catch (CancellationException e) {
                cache.remove(a);
            } catch (ExecutionException e) {
                cache.remove(a);
            }
        }

    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章