高速缓存

package five;
/**
 * 
 * K 表示输入的值,V 表示输出的值
 *
 * @param <K>
 * @param <V>
 */
public interface Computable<K,V> {
    V compute(K arg);
}
package five;

import java.math.BigInteger;

import common.Utils;

public class ExpensiveFunction implements Computable<String, BigInteger> {
    @Override
    public BigInteger compute(String arg) {
	// 一个很长时间的计算
	System.out.println("有人执行我...");
	Utils.sleep(1); // sleep 一会表示执行时间很长
	return new BigInteger(arg);
    }
}

package five;
/**
 * 缓存类
 */
import java.math.BigInteger;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;

public class Memoizer<K,V> implements Computable<K,V>{

    private final Map<K,V> cache = new ConcurrentHashMap<K,V>();// 用concurrentHashMap代替 HashMap
    private final Computable<K,V> c ;
    public Memoizer(Computable<K,V> c) {
	this.c = c;
    }
    public  V compute(K arg) {					// 去掉 synchronized
	V result = cache.get(arg);
	if(result == null)
	{
	    result = c.compute(arg);
	    cache.put(arg,result);
	}
	return result;
    }
    /**
     * 上面的代码存在问题:如果有两个线程同时访问相同 数值,两者都没有找到,则会两个线程都会进入计算
     */
    public static void main(String args[]) {
	final Memoizer<String,BigInteger> m = new Memoizer<String,BigInteger>(new ExpensiveFunction());
	final CountDownLatch gate = new CountDownLatch(2);
	// 创建两个线程,同时向 catche 中取值 
	int n = 2;
	for(int i=0;i<n;i++) {
	    Thread t = new Thread() {
		public void run() {
		    m.compute("11111111111");
		    gate.countDown();
		};
	    };
	    t.start();
	}
	try {
	    gate.await();		//  直到上面两个线程执行结束后再执行
	}catch(InterruptedException e) {
	}
	System.out.println(m.cache.size());
    }
}



上面的代码存在两个线程请求同一个数可能会计算两次的可能,下面是改进的:

package five;

import java.math.BigInteger;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;

public class Memoizers<K, V> implements Computable<K, V> {
    private final ConcurrentMap<K, Future<V>> cache = new ConcurrentHashMap<K, Future<V>>();
    private Computable<K, V> c;

    public Memoizers(Computable<K, V> c) {
	this.c = c;
    }

    public V compute(final K key) {
	Future<V> future = cache.get(key); // get put 为复合操作,不能保证原子性,要用
					   // putIfAbsent
	if (future == null) {
	    Callable<V> call = new Callable<V>() {
		@Override
		public V call() throws Exception {
		    return c.compute(key);
		}
	    };
	    /**
	     * 这里不可以用 put,因为两线程都没有在缓存里找到,当两个线程都执行到这个地方时,它们请求的值可以相同,则 comput方法要执行两次,
	     * 所以要用 putifAbsent,两者的区别是,如果存在,就不向里面添加,而且返回值为原来的值,如果不存在,则向里面添加,返回值为空,我们可以根据返回值
	     * 当开始两个线程都查询到 为 null 时,开始执行这里面的代码,当插入一个的时候,不光插入,我们还能起始查询的作用,如果返回值为原来的值不空,说
	     * 明这个值已经存在了,那么我们就可以执行这个存在的值的 compute,而不用执行两次compute了,这样就不会有两个线程请求同一个数而执行两次了
	     */
	    FutureTask<V> task = new FutureTask<V>(call);
	    future = cache.putIfAbsent(key, task);//putifabsent如果存在则返回原来的,不存在,则返回null,
	    if (future == null) {
		future = task;
		task.run();
	    }
	}

	try {
	    return future.get();
	} catch (InterruptedException e) {
	} catch (ExecutionException e) {
	    e.printStackTrace();
	}
	throw new RuntimeException();
    }

    public static void main(String args[]) {
	final Memoizers<String, BigInteger> m = new Memoizers<String, BigInteger>(
		new ExpensiveFunction());
	int n = 2;
	final CountDownLatch latch = new CountDownLatch(n);
	for (int i = 0; i < n; i++) {
	    Thread t = new Thread() {
		@Override
		public void run() {
		    m.compute("11111111111111111111111");
		    latch.countDown();
		}
	    };
	    t.start();
	}
	try {
	    latch.await();
	} catch (InterruptedException e) {

	}
	System.out.println(m.cache.size());
    }
}


高速缓存_第1张图片

你可能感兴趣的:(高速缓存)