多线程并发的cache实现

本文探讨了在多线程并发场景下,利用Double Check和FutureTask两种模式实现缓存(Cache)的性能和效果。通过性能测试,包括随机分布和数据集中分布的测试案例,对比了两种模式在不同条件下的表现。测试环境为4核8GB的Centos 6.5系统,使用JDK8。结论中提到了在数据集中分布情况下,调整了PrimeUtil的睡眠时间以获得更明显的性能差异。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题描述:

计算输入的数是否是素数是一个耗时的过程,我们现在用cache来存储计算过的值,方便下次检索使用。


cache的两种实现模式:

  • Double Check 实现;
  • FutureTask 实现;


先看计算是否是素数的代码:

package test.concurrency;

import java.util.concurrent.TimeUnit;

public class PrimeUtil {
    
    public static boolean isPrime(long num){
        try {
            // 故意睡会儿,表示这个计算很耗时
            TimeUnit.MILLISECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        if(num == 1){
            return false;
        }else if(num == 2){
            return true;
        }else{
            for(int n = 2; n<=Math.sqrt(num); n++){
                if(num%n == 0){
                    return false;
                }
            }
            return true;
        }
    }
    
}



Double Check 版cache的实现:

package test.concurrency;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class PrimeDoubleCheckCache {
    
    private ConcurrentMap<Long, Boolean> primes;
    private Lock lock = new ReentrantLock();
    
    public PrimeDoubleCheckCache(int size){
        primes = new ConcurrentHashMap<>(size*3/2);
    }
    
    public boolean isPrime(long num){
        Boolean r = primes.get(num);
        if(r == null){
            try{
                lock.lock();
                if(r == null){
                    r = PrimeUtil.isPrime(num);
                }
            }finally{
                lock.unlock();
            }
            primes.putIfAbsent(num, r);
        }
        return r;
    }
}



FutureTask 版cache的实现:

package test.concurrency;

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

public class PrimeFutureCache {
    
    private ConcurrentMap<Long, Future<Boolean>> primes;
    
    public PrimeFutureCache(int size){
        primes = new ConcurrentHashMap<>(size*3/2);
    }
    
    public boolean isPrime(long num){
        while(true){
            try{
                Future<Boolean> f = primes.get(num);
                if(f == null){
                    FutureTask<Boolean> task = new FutureTask<>(new Callable<Boolean>(){
        
                        @Override
                        public Boolean call() throws Exception {
                            return PrimeUtil.isPrime(num);
                        }
                        
                    });
                    Future<Boolean> f1 = primes.putIfAbsent(num, task);
                    if(f1 == null){
                        task.run();
                        f = task;
                    }else{
                        f = f1;
                    }
                }
                return f.get();
            }catch(CancellationException ce){
                primes.remove(num);
            }catch(Exception ee){
                throw loadException(ee);
            }
        }
    }
    
    public RuntimeException loadException(Throwable ee){
        if(ee instanceof RuntimeException){
            return (RuntimeException) ee;
        }else {
            throw new IllegalStateException("not check", ee);
        }
    }

}




性能测试:

测试使用了30个线程,10W数据,分为两种情况模拟测试,

1) 数据随机分布;

2) 数据集中分布;


double check 随机分布测试:

static class DoubleRandomTask implements Runnable {
		
		CountDownLatch cdl;
		PrimeDoubleCheckCache cache;
		
		public DoubleRandomTask(CountDownLatch cdl, PrimeDoubleCheckCache cache){
			this.cache = cache;
			this.cdl = cdl;
		}
		
		public void run(){
			
			for(int i=0; i<COUNT; i++){
				int num = ThreadLocalRandom.current().nextInt(COUNT);
				boolean b = cache.isPrime(num);
				System.out.println("index: "+(i+1)+", num: "+num+", isPrime: "+b);
			}
			
			cdl.countDown();
			
		}
	}

static void randomDouble(){
		CountDownLatch cdl = new CountDownLatch(THREAD_NUM);
		PrimeDoubleCheckCache cache = new PrimeDoubleCheckCache(COUNT);
		ExecutorService s = Executors.newFixedThreadPool(THREAD_NUM);
		
		long start = System.currentTimeMillis();
		for(int i=0; i<THREAD_NUM; i++){
			s.execute(new DoubleRandomTask(cdl, cache));
		}
		
		try {
			cdl.await();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		long end = System.currentTimeMillis();
		System.out.println("Double check cache cost time: "+(end-start)+" ms");
		
		s.shutdown();
	}

FutureTask 随机分布测试:

static class FutureRandomTask implements Runnable {
		
		CountDownLatch cdl;
		PrimeFutureCache cache;
		
		public FutureRandomTask(CountDownLatch cdl, PrimeFutureCache cache){
			this.cache = cache;
			this.cdl = cdl;
		}
		
		public void run(){
			
			for(int i=0; i<COUNT; i++){
				int num = ThreadLocalRandom.current().nextInt(COUNT);
				boolean b = cache.isPrime(num);
				System.out.println("index: "+(i+1)+", num: "+num+", isPrime: "+b);
			}
			
			cdl.countDown();
			
		}
	}

static void randomFuture(){
		CountDownLatch cdl = new CountDownLatch(THREAD_NUM);
		PrimeFutureCache cache = new PrimeFutureCache(COUNT);
		ExecutorService s = Executors.newFixedThreadPool(THREAD_NUM);
		
		long start = System.currentTimeMillis();
		for(int i=0; i<THREAD_NUM; i++){
			s.execute(new FutureRandomTask(cdl, cache));
		}
		
		try {
			cdl.await();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		long end = System.currentTimeMillis();
		System.out.println("Double check cache cost time: "+(end-start)+" ms");
		
		s.shutdown();
	}

Double Check 数据集中分布测试:

static class DoubleCentralTask implements Runnable {
		
		CountDownLatch cdl;
		PrimeDoubleCheckCache cache;
		
		public DoubleCentralTask(CountDownLatch cdl, PrimeDoubleCheckCache cache){
			this.cache = cache;
			this.cdl = cdl;
		}
		
		public void run(){
			
			for(int i=0; i<COUNT; i++){
				long num = System.currentTimeMillis()/1000;
				boolean b = cache.isPrime(num);
				System.out.println("index: "+(i+1)+", num: "+num+", isPrime: "+b);
			}
			
			cdl.countDown();
			
		}
	}

static void centralDouble(){
		CountDownLatch cdl = new CountDownLatch(THREAD_NUM);
		PrimeDoubleCheckCache cache = new PrimeDoubleCheckCache(COUNT);
		ExecutorService s = Executors.newFixedThreadPool(THREAD_NUM);
		
		long start = System.currentTimeMillis();
		for(int i=0; i<THREAD_NUM; i++){
			s.execute(new DoubleCentralTask(cdl, cache));
		}
		
		try {
			cdl.await();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		long end = System.currentTimeMillis();
		System.out.println("Double check cache cost time: "+(end-start)+" ms");
		
		s.shutdown();
	}

FutureTask 数据集中分布测试:

static class FutureCentralTask implements Runnable {
		
		CountDownLatch cdl;
		PrimeFutureCache cache;
		
		public FutureCentralTask(CountDownLatch cdl, PrimeFutureCache cache){
			this.cache = cache;
			this.cdl = cdl;
		}
		
		public void run(){
			
			for(int i=0; i<COUNT; i++){
				long num = System.currentTimeMillis()/1000;
				boolean b = cache.isPrime(num);
				System.out.println("index: "+(i+1)+", num: "+num+", isPrime: "+b);
			}
			
			cdl.countDown();
			
		}
	}

static void centralFuture(){
		CountDownLatch cdl = new CountDownLatch(THREAD_NUM);
		PrimeFutureCache cache = new PrimeFutureCache(COUNT);
		ExecutorService s = Executors.newFixedThreadPool(THREAD_NUM);
		
		long start = System.currentTimeMillis();
		for(int i=0; i<THREAD_NUM; i++){
			s.execute(new FutureCentralTask(cdl, cache));
		}
		
		try {
			cdl.await();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		long end = System.currentTimeMillis();
		System.out.println("Double check cache cost time: "+(end-start)+" ms");
		
		s.shutdown();
	}


测试代码汇总:

package test.concurrency;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadLocalRandom;

public class PrimeTest {
    
    static final int COUNT = 100_000;
    static final int THREAD_NUM = 30;

    public static void main(String[] args) {
        randomDouble();
    }
    
    static void centralFuture(){
        CountDownLatch cdl = new CountDownLatch(THREAD_NUM);
        PrimeFutureCache cache = new PrimeFutureCache(COUNT);
        ExecutorService s = Executors.newFixedThreadPool(THREAD_NUM);
        
        long start = System.currentTimeMillis();
        for(int i=0; i<THREAD_NUM; i++){
            s.execute(new FutureCentralTask(cdl, cache));
        }
        
        try {
            cdl.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("Double check cache cost time: "+(end-start)+" ms");
        
        s.shutdown();
    }
    
    static void centralDouble(){
        CountDownLatch cdl = new CountDownLatch(THREAD_NUM);
        PrimeDoubleCheckCache cache = new PrimeDoubleCheckCache(COUNT);
        ExecutorService s = Executors.newFixedThreadPool(THREAD_NUM);
        
        long start = System.currentTimeMillis();
        for(int i=0; i<THREAD_NUM; i++){
            s.execute(new DoubleCentralTask(cdl, cache));
        }
        
        try {
            cdl.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("Double check cache cost time: "+(end-start)+" ms");
        
        s.shutdown();
    }
    
    static void randomFuture(){
        CountDownLatch cdl = new CountDownLatch(THREAD_NUM);
        PrimeFutureCache cache = new PrimeFutureCache(COUNT);
        ExecutorService s = Executors.newFixedThreadPool(THREAD_NUM);
        
        long start = System.currentTimeMillis();
        for(int i=0; i<THREAD_NUM; i++){
            s.execute(new FutureRandomTask(cdl, cache));
        }
        
        try {
            cdl.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("Double check cache cost time: "+(end-start)+" ms");
        
        s.shutdown();
    }
    
    static void randomDouble(){
        CountDownLatch cdl = new CountDownLatch(THREAD_NUM);
        PrimeDoubleCheckCache cache = new PrimeDoubleCheckCache(COUNT);
        ExecutorService s = Executors.newFixedThreadPool(THREAD_NUM);
        
        long start = System.currentTimeMillis();
        for(int i=0; i<THREAD_NUM; i++){
            s.execute(new DoubleRandomTask(cdl, cache));
        }
        
        try {
            cdl.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("Double check cache cost time: "+(end-start)+" ms");
        
        s.shutdown();
    }
    
    static class DoubleRandomTask implements Runnable {
        
        CountDownLatch cdl;
        PrimeDoubleCheckCache cache;
        
        public DoubleRandomTask(CountDownLatch cdl, PrimeDoubleCheckCache cache){
            this.cache = cache;
            this.cdl = cdl;
        }
        
        public void run(){
            
            for(int i=0; i<COUNT; i++){
                int num = ThreadLocalRandom.current().nextInt(COUNT);
                boolean b = cache.isPrime(num);
                System.out.println("index: "+(i+1)+", num: "+num+", isPrime: "+b);
            }
            
            cdl.countDown();
            
        }
    }
    
    static class DoubleCentralTask implements Runnable {
        
        CountDownLatch cdl;
        PrimeDoubleCheckCache cache;
        
        public DoubleCentralTask(CountDownLatch cdl, PrimeDoubleCheckCache cache){
            this.cache = cache;
            this.cdl = cdl;
        }
        
        public void run(){
            
            for(int i=0; i<COUNT; i++){
                long num = System.currentTimeMillis()/1000;
                boolean b = cache.isPrime(num);
                System.out.println("index: "+(i+1)+", num: "+num+", isPrime: "+b);
            }
            
            cdl.countDown();
            
        }
    }

    static class FutureRandomTask implements Runnable {
        
        CountDownLatch cdl;
        PrimeFutureCache cache;
        
        public FutureRandomTask(CountDownLatch cdl, PrimeFutureCache cache){
            this.cache = cache;
            this.cdl = cdl;
        }
        
        public void run(){
            
            for(int i=0; i<COUNT; i++){
                int num = ThreadLocalRandom.current().nextInt(COUNT);
                boolean b = cache.isPrime(num);
                System.out.println("index: "+(i+1)+", num: "+num+", isPrime: "+b);
            }
            
            cdl.countDown();
            
        }
    }
    
    static class FutureCentralTask implements Runnable {
        
        CountDownLatch cdl;
        PrimeFutureCache cache;
        
        public FutureCentralTask(CountDownLatch cdl, PrimeFutureCache cache){
            this.cache = cache;
            this.cdl = cdl;
        }
        
        public void run(){
            
            for(int i=0; i<COUNT; i++){
                long num = System.currentTimeMillis()/1000;
                boolean b = cache.isPrime(num);
                System.out.println("index: "+(i+1)+", num: "+num+", isPrime: "+b);
            }
            
            cdl.countDown();
            
        }
    }

}




最终结果比较:     运行环境( 4核,8G, Centos 6.5, JDK8)

性能对比
次数\类别Random Double Check
( sleep 5ms)
Random Future Task
 (sleep 5ms)
Central Double Check
 (sleep 100ms)
Central Future Task
 (sleep 100ms)
1579263 ms 94633 ms  121365 ms  94989 ms
2579681 ms94075 ms  131892 ms95076 ms
3580306 ms 94548 ms122636 ms  95293 ms

数据集中分布时,睡眠时间过短不易区分二者性能,故调整PrimeUtil的睡眠时间到100ms。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值