使用ConcurrentHashMap中的putIfAbsent为计算结果建立高效、可伸缩的高速缓存

1、使用HashMap实现缓存

代码如下:

/**
 * Memoizer1
 *
 * Initial cache attempt using HashMap and synchronization
 *
 * @author Brian Goetz and Tim Peierls
 */
public class Memoizer1 <A, V> implements Computable<A, V> {
    @GuardedBy("this") private final Map<A, V> cache = new HashMap<A, V>();
    private final Computable<A, V> c;

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

    public synchronized V compute(A arg) throws InterruptedException {
        V result = cache.get(arg);
        if (result == null) {
            result = c.compute(arg);
            cache.put(arg, result);
        }
        return result;
    }
}


interface Computable <A, V> {
    V compute(A arg) throws InterruptedException;
}

class ExpensiveFunction
        implements Computable<String, BigInteger> {
    public BigInteger compute(String arg) {
        // after deep thought...
        return new BigInteger(arg);
    }
}

使用HashMap存储前面的计算结果,compute方法首先检查期待的结果是否已经在缓存中,如果有则返回之前计算的数值。否则,会进行计算并在返回之前将结果存储在HashMap中。

HashMap不是线程安全的,所以使用synchronized来同步了整个compute方法,一次只能由一个线程可以执行compute。如果正在执行的方法计算结果需要比较长的时间,则其他多个线程需要阻塞等待。

2、使用ConcurrentHashMap取代HashMap。ConcurrentHashMap是线程安全的并发容器,所以不需要额外的解锁操作。

代码如下:

/**
 * Memoizer2
 * <p/>
 * Replacing HashMap with ConcurrentHashMap
 *
 * @author Brian Goetz and Tim Peierls
 */
public class Memoizer2 <A, V> implements Computable<A, V> {
    private final Map<A, V> cache = new ConcurrentHashMap<A, V>();
    private final Computable<A, V> c;

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

    public V compute(A arg) throws InterruptedException {
        V result = cache.get(arg);
        if (result == null) {
            result = c.compute(arg);
            cache.put(arg, result);
        }
        return result;
    }
}

但是在多个线程同时调用compute方法是,存在并发问题。第一个进入的线程由于计算时间长,第二个线程进入时,并没有查询到结果,所以也进入计算,这样就产生了重复计算相同的数据的情况。

3、为了能够让之后进入的线程看到已有线程正在执行任务,可以使用FutureTask来作为缓存的值。FutureTask代表了一个计算的过程,可能已经结束,也可能正在运行中。FutureTask.get()只要结果可用,就会立刻将结果返回;否则它会一直阻塞,直到结果被计算出来,并返回。

使用ConcurrentHashMap<A,Future<V>>取代ConcurrentHashMap<A,V>,首先检查一个相应的计算是否已经开始。如果没有,就创建一个FutureTask,把它注册到Map中,并开始计算;如果有,那么久等待正在进行的计算。

代码如下:

/**
 * Memoizer3
 * <p/>
 * Memoizing wrapper using FutureTask
 *
 * @author Brian Goetz and Tim Peierls
 */
public class Memoizer3 <A, V> implements Computable<A, V> {
    private final Map<A, Future<V>> cache
            = new ConcurrentHashMap<A, Future<V>>();
    private final Computable<A, V> c;

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

    public V compute(final A arg) throws InterruptedException {
        Future<V> f = cache.get(arg);
        if (f == null) {
            Callable<V> eval = new Callable<V>() {
                public V call() throws InterruptedException {
                    return c.compute(arg);
                }
            };
            FutureTask<V> ft = new FutureTask<V>(eval);
            f = ft;
            cache.put(arg, ft);
            ft.run(); // call to c.compute happens here
        }
        try {
            return f.get();
        } catch (ExecutionException e) {
            throw LaunderThrowable.launderThrowable(e.getCause());
        }
    }
}

这个方式存在于之前实现方式类似的问题,多个线程同时进入compute方法时,由于compute中的if代码块是非原子性的“检查再运行”,可能两个线程几乎在同一时间调用compute计算相同的值,双方都没有在缓存中找到期望的值,并都开始计算。

4、利用ConcurrentHashMap中原子操作putIfAbsent方法,如果没有则放入

代码如下:

/**
 * Memoizer
 * <p/>
 * Final implementation of Memoizer
 *
 * @author Brian Goetz and Tim Peierls
 */
public class Memoizer <A, V> implements Computable<A, V> {
    private final ConcurrentMap<A, Future<V>> cache
            = new ConcurrentHashMap<A, Future<V>>();
    private final Computable<A, V> c;

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

    public V compute(final A arg) throws InterruptedException {
        while (true) {
            Future<V> f = cache.get(arg);
            if (f == null) {
                Callable<V> eval = new Callable<V>() {
                    public V call() throws InterruptedException {
                        return c.compute(arg);
                    }
                };
                FutureTask<V> ft = new FutureTask<V>(eval);
                f = cache.putIfAbsent(arg, ft);
                if (f == null) {
                    f = ft;
                    ft.run();
                }
            }
            try {
                return f.get();
            } catch (CancellationException e) {
                cache.remove(arg, f);
            } catch (ExecutionException e) {
                throw LaunderThrowable.launderThrowable(e.getCause());
            }
        }
    }
}

猜你喜欢

转载自blog.csdn.net/u014653854/article/details/80633911