问题描述:
计算输入的数是否是素数是一个耗时的过程,我们现在用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) |
---|---|---|---|---|
1 | 579263 ms | 94633 ms | 121365 ms | 94989 ms |
2 | 579681 ms | 94075 ms | 131892 ms | 95076 ms |
3 | 580306 ms | 94548 ms | 122636 ms | 95293 ms |
数据集中分布时,睡眠时间过短不易区分二者性能,故调整PrimeUtil的睡眠时间到100ms。