【JAVA并发编程实战】5、构建高效且可伸缩的结果缓存

首先创建一个借口,用来表示耗费资源的计算

package cn.xf.cp.ch05;

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

实现接口,实现计算过程

package cn.xf.cp.ch05;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.BigInteger;

public class ExpensiveFunction implements Computable<String, BigInteger>
{

    @Override
    public BigInteger compute(String arg) throws InterruptedException
    {
        //读取10个文件的和
        BigInteger count = new BigInteger("0");
        File file = null;
        InputStreamReader is = null;
        BufferedReader br = null;
        try
        {
            file = new File(String.valueOf(arg));
            is = new InputStreamReader(new FileInputStream(file));
            br = new BufferedReader(is);
            String line = "";
            while((line = br.readLine()) != null)
            {
                String longdata[] = line.split(" ");
                for(int j = 0; j < longdata.length; ++j)
                {
                    //统计和
                    String temp = longdata[j];
                    BigInteger bitemp = new BigInteger(temp);
                    count = count.add(bitemp);
                }
            }
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        finally
        {
            try
            {
                br.close();
                is.close();
            }
            catch (IOException e)
            {
                e.printStackTrace();
            }
        }
        //返回统计结果
        return count;
    }
    
    @org.junit.Test
    public void test()
    {
        String s = "132456789123456789326549873218746132165432176134653216874649761";
        BigInteger bi = new BigInteger("0");
        
        System.out.println(bi.add(new BigInteger("1")).toString());
    }
}

功能实现1,这个不是现场安全的,就是一个简单的缓存机制,如果有就直接从缓存中获取,没有的话就添加到缓存中

package cn.xf.cp.ch05;

import java.util.HashMap;
import java.util.Map;

public class Memoizer1<A, V> implements Computable<A, V>
{
    /**
     * 这里使用hashmap来作为缓存对象,其实并不是一个好的选择,hashmap并不是一个线程安全的类
     */
    private final Map<A, V> cache = new HashMap<A, V>();
    private final Computable<A, V> c;
    
    public Memoizer1(Computable<A, V> c)
    {
        //初始化C的值
        this.c = c;
    }
    
    /**
     *    由于hashmap并不是一个线程安全的,所以我们队这个操作加锁,避免意外
     *    但是如果有一个问题,如果一个线程阻塞了这个方法,那么其他线程就无法通过这个方法获取到对应的缓存
     *    那么就可能会造成使用缓存的结果却比不使用缓存更慢,性能并不会得到任何的提升
     * @param arg
     * @return
     * @throws InterruptedException
     */
    @Override
    public synchronized V compute(A arg) throws Exception
    {
        V result = cache.get(arg);
        if(result == null)
        {
            result = c.compute(arg);
            //如果没有的话,那么就放到对应的缓存对象中
            cache.put(arg, result);
        }
        return result;
    }

}

功能实现2:添加一个future进行异步处理,就是把原来的数据计量分化到异步处理中,这样就不会产生阻塞在一个地方,造成其他线程等待很耗费资源的计算产生结果的问题

package cn.xf.cp.ch05;

import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;

public class Memoizer2<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 Memoizer2(Computable<A, V> c) 
    {
        this.c = c;
    }
    
    /**
     * 这个里面的复合操作,没有就添加future,是在底层的MAP对象上执行的,而map无法通过加锁来确定原子性
     */
    @Override
    public V compute(final A arg) throws Exception
    {
        Future<V> f = cache.get(arg);
        if (f == null)
        {
            Callable<V> eval = new Callable<V>()
            {
                public V call() throws Exception
                {
                    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
        }
        return f.get();
    }
    
}

功能实现3:避免底层map的操作有影响,并防止缓存污染

package cn.xf.cp.ch05;

import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;

public class Memoizer3<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 Memoizer3(Computable<A, V> c)
    {
        this.c = c;
    }
    
    @Override
    public V compute(final A arg) throws Exception
    {
        //不断循环,直到return
        while (true)
        {
            Future<V> f = cache.get(arg);
            if (f == null)
            {
                Callable<V> eval = new Callable<V>()
                {
                    public V call() throws Exception
                    {
                        return c.compute(arg);
                    }
                };
                FutureTask<V> ft = new FutureTask<V>(eval);
                /*
                 * 确定使用,这个避免重复添加
                 * 如果包含arg就返回,否则就把ft放入进去
                 */
                f = cache.putIfAbsent(arg, ft);
                if (f == null)
                {
                    f = ft;
                    ft.run();
                }
            }
            try
            {
                //返回计算结果
                return f.get();
            }
            catch (Exception e)
            {
                //计算失败,从缓存中移除,避免缓存污染,就是一个key返回一个空值,并返回从新运算
                cache.remove(arg, f);
            }
        }
    }

}

测试结果:

package cn.xf.cp.ch05;

import java.math.BigInteger;

public class MemeryTest
{
    public static void main(String[] args) throws Exception
    {
        ExpensiveFunction ef = new ExpensiveFunction();
        Memoizer1<String, BigInteger> m = new Memoizer1<String, BigInteger>(ef);
        BigInteger bresult1 = new BigInteger("0");
        BigInteger bresult2 = new BigInteger("0");
        //比较不同方式统计10次1文件的时间比较
        long start1 = System.nanoTime();
        for(int i = 1; i <= 10; ++i)
        {
            bresult1 = bresult1.add(ef.compute(String.valueOf(1)));
        }
        long end1 = System.nanoTime();
        
        long start2 = System.nanoTime();
        for(int i = 1; i <= 10; ++i)
        {
            bresult2 = bresult2.add(m.compute(String.valueOf(1)));
        }
        long end2 = System.nanoTime();
        
        System.out.println("统计文件1~10使用时间" + (end1 - start1) + "  大小是:" + bresult1.toString());
        
        System.out.println("统计文件1,10次使用时间" + (end2 - start2) + "  大小是:" + bresult2.toString());
    }
    
    @org.junit.Test
    public void test2() throws Exception
    {
        ExpensiveFunction ef = new ExpensiveFunction();
        Memoizer2<String, BigInteger> m = new Memoizer2<String, BigInteger>(ef);
        BigInteger bresult1 = new BigInteger("0");
        BigInteger bresult2 = new BigInteger("0");
        //比较不同方式统计10次1文件的时间比较
        long start1 = System.nanoTime();
        for(int i = 1; i <= 10; ++i)
        {
            bresult1 = bresult1.add(ef.compute(String.valueOf(1)));
        }
        long end1 = System.nanoTime();
        
        long start2 = System.nanoTime();
        for(int i = 1; i <= 10; ++i)
        {
            bresult2 = bresult2.add(m.compute(String.valueOf(1)));
        }
        long end2 = System.nanoTime();
        
        System.out.println("统计文件1~10使用时间" + (end1 - start1) + "  大小是:" + bresult1.toString());
        
        System.out.println("统计文件1,10次使用时间" + (end2 - start2) + "  大小是:" + bresult2.toString());
    
    }
    
    @org.junit.Test
    public void test3() throws Exception
    {

        ExpensiveFunction ef = new ExpensiveFunction();
        Memoizer3<String, BigInteger> m = new Memoizer3<String, BigInteger>(ef);
        BigInteger bresult1 = new BigInteger("0");
        BigInteger bresult2 = new BigInteger("0");
        //比较不同方式统计10次1文件的时间比较
        long start1 = System.nanoTime();
        for(int i = 1; i <= 10; ++i)
        {
            bresult1 = bresult1.add(ef.compute(String.valueOf(1)));
        }
        long end1 = System.nanoTime();
        
        long start2 = System.nanoTime();
        for(int i = 1; i <= 10; ++i)
        {
            bresult2 = bresult2.add(m.compute(String.valueOf(1)));
        }
        long end2 = System.nanoTime();
        
        System.out.println("统计文件1~10使用时间" + (end1 - start1) + "  大小是:" + bresult1.toString());
        
        System.out.println("统计文件1,10次使用时间" + (end2 - start2) + "  大小是:" + bresult2.toString());
    
    
    }
}

结果就是使用了缓存的话,速度会快10倍,这个决定于循环的次数

文件的话,就是简单的循环生成数据

 

三种测试结果,差不多,因为这里并没有使用到多线程进行测试,结果和单线程是一样的。 

原文地址:https://www.cnblogs.com/cutter-point/p/6017631.html