Java 高并发缓存与Guava Cache

一.背景

缓存是我们在开发中为了提高系统的性能,把经常的访问业务的数据第一次把处理结果先放到缓存中,第二次就不用在对相同的业务数据在重新处理一遍,这样就提高了系统的性能。缓存分好几种:

(1)本地缓存。

(2)数据库缓存。

(3)分布式缓存。

分布式缓存比较常用的有memcached等,memcached是高性能的分布式内存缓存服务器,缓存业务处理结果,减少数据库访问次数和相同复杂逻辑处理的时间,以提高动态Web应用的速度、 提高可扩展性

二.本地缓存在高并发下的问题以及解决

今天我们介绍的是本地缓存缓存,我们这边采用java.util.concurrent.ConcurrentHashMap来保存,ConcurrentHashMap是一个线程安全的HashTable,并提供了一组和HashTable功能相同但是线程安全的方法,ConcurrentHashMap可以做到读取数据不加锁,提高了并发能力。我们先不考虑内存元素回收或者在保存数据会出现内存溢出的情况,我们用ConcurrentHashMap模拟本地缓存,当在高并发环境一下,会出现一些什么问题?


我们这边采用实现多个线程来模拟高并发场景。


第一种:我们先来看一下代码:


  1. public class TestConcurrentHashMapCache<K,V> {
  2. private final ConcurrentHashMap<K, V> cacheMap=new ConcurrentHashMap<K,V> ();
  3. public Object getCache(K keyValue,String ThreadName){
  4. System.out.println("ThreadName getCache=============="+ThreadName);
  5. Object value=null;
  6. //从缓存获取数据
  7. value=cacheMap.get(keyValue);
  8. //如果没有的话,把数据放到缓存
  9. if(value==null){
  10. return putCache(keyValue,ThreadName);
  11. }
  12. return value;
  13. }
  14. public Object putCache(K keyValue,String ThreadName){
  15. System.out.println("ThreadName 执行业务数据并返回处理结果的数据(访问数据库等)=============="+ThreadName);
  16. //可以根据业务从数据库获取等取得数据,这边就模拟已经获取数据了
  17. @SuppressWarnings("unchecked")
  18. V value=(V) "dataValue";
  19. //把数据放到缓存
  20. cacheMap.put(keyValue, value);
  21. return value;
  22. }
  23. public static void main(String[] args) {
  24. final TestConcurrentHashMapCache<String,String> TestGuaVA=new TestConcurrentHashMapCache<String,String>();
  25. Thread t1=new Thread(new Runnable() {
  26. @Override
  27. public void run() {
  28. System.out.println("T1======start========");
  29. Object value=TestGuaVA.getCache("key","T1");
  30. System.out.println("T1 value=============="+value);
  31. System.out.println("T1======end========");
  32. }
  33. });
  34. Thread t2=new Thread(new Runnable() {
  35. @Override
  36. public void run() {
  37. System.out.println("T2======start========");
  38. Object value=TestGuaVA.getCache("key","T2");
  39. System.out.println("T2 value=============="+value);
  40. System.out.println("T2======end========");
  41. }
  42. });
  43. Thread t3=new Thread(new Runnable() {
  44. @Override
  45. public void run() {
  46. System.out.println("T3======start========");
  47. Object value=TestGuaVA.getCache("key","T3");
  48. System.out.println("T3 value=============="+value);
  49. System.out.println("T3======end========");
  50. }
  51. });
  52. t1.start();
  53. t2.start();
  54. t3.start();
  55. }
  56. }
public class TestConcurrentHashMapCache<K,V> {
	private final ConcurrentHashMap<K, V>  cacheMap=new ConcurrentHashMap<K,V> (); 
	
	public  Object getCache(K keyValue,String ThreadName){
		System.out.println("ThreadName getCache=============="+ThreadName);
		Object value=null;
		//从缓存获取数据
		value=cacheMap.get(keyValue);
		//如果没有的话,把数据放到缓存
		if(value==null){
			return putCache(keyValue,ThreadName);
		}
		return value;
	}
	
	public Object putCache(K keyValue,String ThreadName){
		System.out.println("ThreadName 执行业务数据并返回处理结果的数据(访问数据库等)=============="+ThreadName);
		//可以根据业务从数据库获取等取得数据,这边就模拟已经获取数据了
		@SuppressWarnings("unchecked")
		V value=(V) "dataValue";
		//把数据放到缓存
		 cacheMap.put(keyValue, value);
		return value;
	}

	
	
	public static void main(String[] args) {
		final TestConcurrentHashMapCache<String,String> TestGuaVA=new TestConcurrentHashMapCache<String,String>();
		
		Thread t1=new Thread(new Runnable() {
			@Override
			public void run() {
				
				System.out.println("T1======start========");
			    Object value=TestGuaVA.getCache("key","T1");
			    System.out.println("T1 value=============="+value);
				System.out.println("T1======end========");
				
			}
		});
		
		Thread t2=new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println("T2======start========");
				Object value=TestGuaVA.getCache("key","T2");
			    System.out.println("T2 value=============="+value);
				System.out.println("T2======end========");
				
			}
		});
		
		Thread t3=new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println("T3======start========");
				Object value=TestGuaVA.getCache("key","T3");
			    System.out.println("T3 value=============="+value);
				System.out.println("T3======end========");
				
			}
		});
		
		t1.start();
		t2.start();
		t3.start();

	}

}
我们看一下执行结果,如图所示:

我们实现了本地缓存代码,我们执行一下结果,发现在多线程时,出现了在缓存里没有缓存时,会执行一样执行多次的业务数据并返回处理的数据,我们分析一下出现这种情况的:

(1)当线程T1访问cacheMap里面有没有,这时根据业务到后台处理业务数据并返回处理数据,并放入缓存

(2)当线程T2访问cacheMap里面同样也没有,也把根据业务到后台处理业务数据并返回处理数据,并放入缓存

第二种:

这样相同的业务并处理两遍,如果在高并发的情况下相同的业务不止执行两遍,这样这样跟我们当初做缓存不相符合,这时我们想到了Java多线程时,在执行获取缓存上加上Synchronized,代码如下:

  1. public class TestConcurrentHashMapCache<K,V> {
  2. private final ConcurrentHashMap<K, V> cacheMap=new ConcurrentHashMap<K,V> ();
  3. public <span style="color:#ff0000;">synchronized </span>Object getCache(K keyValue,String ThreadName){
  4. System.out.println("ThreadName getCache=============="+ThreadName);
  5. Object value=null;
  6. //从缓存获取数据
  7. value=cacheMap.get(keyValue);
  8. //如果没有的话,把数据放到缓存
  9. if(value==null){
  10. return putCache(keyValue,ThreadName);
  11. }
  12. return value;
  13. }
  14. public Object putCache(K keyValue,String ThreadName){
  15. System.out.println("ThreadName 执行业务数据并返回处理结果的数据(访问数据库等)=============="+ThreadName);
  16. //可以根据业务从数据库获取等取得数据,这边就模拟已经获取数据了
  17. @SuppressWarnings("unchecked")
  18. V value=(V) "dataValue";
  19. //把数据放到缓存
  20. cacheMap.put(keyValue, value);
  21. return value;
  22. }
  23. public static void main(String[] args) {
  24. final TestConcurrentHashMapCache<String,String> TestGuaVA=new TestConcurrentHashMapCache<String,String>();
  25. Thread t1=new Thread(new Runnable() {
  26. @Override
  27. public void run() {
  28. System.out.println("T1======start========");
  29. Object value=TestGuaVA.getCache("key","T1");
  30. System.out.println("T1 value=============="+value);
  31. System.out.println("T1======end========");
  32. }
  33. });
  34. Thread t2=new Thread(new Runnable() {
  35. @Override
  36. public void run() {
  37. System.out.println("T2======start========");
  38. Object value=TestGuaVA.getCache("key","T2");
  39. System.out.println("T2 value=============="+value);
  40. System.out.println("T2======end========");
  41. }
  42. });
  43. Thread t3=new Thread(new Runnable() {
  44. @Override
  45. public void run() {
  46. System.out.println("T3======start========");
  47. Object value=TestGuaVA.getCache("key","T3");
  48. System.out.println("T3 value=============="+value);
  49. System.out.println("T3======end========");
  50. }
  51. });
  52. t1.start();
  53. t2.start();
  54. t3.start();
  55. }
  56. }
public class TestConcurrentHashMapCache<K,V> {
	private final ConcurrentHashMap<K, V>  cacheMap=new ConcurrentHashMap<K,V> (); 
	
	public <span style="color:#ff0000;">synchronized </span>Object getCache(K keyValue,String ThreadName){
		System.out.println("ThreadName getCache=============="+ThreadName);
		Object value=null;
		//从缓存获取数据
		value=cacheMap.get(keyValue);
		//如果没有的话,把数据放到缓存
		if(value==null){
			return putCache(keyValue,ThreadName);
		}
		return value;
	}
	
	public Object putCache(K keyValue,String ThreadName){
		System.out.println("ThreadName 执行业务数据并返回处理结果的数据(访问数据库等)=============="+ThreadName);
		//可以根据业务从数据库获取等取得数据,这边就模拟已经获取数据了
		@SuppressWarnings("unchecked")
		V value=(V) "dataValue";
		//把数据放到缓存
		 cacheMap.put(keyValue, value);
		return value;
	}

	
	
	public static void main(String[] args) {
		final TestConcurrentHashMapCache<String,String> TestGuaVA=new TestConcurrentHashMapCache<String,String>();
		
		Thread t1=new Thread(new Runnable() {
			@Override
			public void run() {
				
				System.out.println("T1======start========");
			    Object value=TestGuaVA.getCache("key","T1");
			    System.out.println("T1 value=============="+value);
				System.out.println("T1======end========");
				
			}
		});
		
		Thread t2=new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println("T2======start========");
				Object value=TestGuaVA.getCache("key","T2");
			    System.out.println("T2 value=============="+value);
				System.out.println("T2======end========");
				
			}
		});
		
		Thread t3=new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println("T3======start========");
				Object value=TestGuaVA.getCache("key","T3");
			    System.out.println("T3 value=============="+value);
				System.out.println("T3======end========");
				
			}
		});
		
		t1.start();
		t2.start();
		t3.start();

	}

}
执行结果,如图所示:

这样就实现了串行,在高并发行时,就不会出现了第二个访问相同业务,肯定是从缓存获取,但是加上Synchronized变成串行,这样在高并发行时性能也下降了。

第三种:

我们为了实现性能和缓存的结果,我们采用Future,因为Future在计算完成时获取,否则会一直阻塞直到任务转入完成状态和ConcurrentHashMap.putIfAbsent方法,代码如下:

  1. public class TestFutureCahe<K,V> {
  2. private final ConcurrentHashMap<K, Future<V>> cacheMap=new ConcurrentHashMap<K, Future<V>> ();
  3. public Object getCache(K keyValue,String ThreadName){
  4. Future<V> value=null;
  5. try{
  6. System.out.println("ThreadName getCache=============="+ThreadName);
  7. //从缓存获取数据
  8. value=cacheMap.get(keyValue);
  9. //如果没有的话,把数据放到缓存
  10. if(value==null){
  11. value= putCache(keyValue,ThreadName);
  12. return value.get();
  13. }
  14. return value.get();
  15. }catch (Exception e) {
  16. }
  17. return null;
  18. }
  19. public Future<V> putCache(K keyValue,final String ThreadName){
  20. // //把数据放到缓存
  21. Future<V> value=null;
  22. Callable<V> callable=new Callable<V>() {
  23. @SuppressWarnings("unchecked")
  24. @Override
  25. public V call() throws Exception {
  26. //可以根据业务从数据库获取等取得数据,这边就模拟已经获取数据了
  27. System.out.println("ThreadName 执行业务数据并返回处理结果的数据(访问数据库等)=============="+ThreadName);
  28. return (V) "dataValue";
  29. }
  30. };
  31. FutureTask<V> futureTask=new FutureTask<V>(callable);
  32. value=cacheMap.putIfAbsent(keyValue, futureTask);
  33. if(value==null){
  34. value=futureTask;
  35. futureTask.run();
  36. }
  37. return value;
  38. }
  39. public static void main(String[] args) {
  40. final TestFutureCahe<String,String> TestGuaVA=new TestFutureCahe<String,String>();
  41. Thread t1=new Thread(new Runnable() {
  42. @Override
  43. public void run() {
  44. System.out.println("T1======start========");
  45. Object value=TestGuaVA.getCache("key","T1");
  46. System.out.println("T1 value=============="+value);
  47. System.out.println("T1======end========");
  48. }
  49. });
  50. Thread t2=new Thread(new Runnable() {
  51. @Override
  52. public void run() {
  53. System.out.println("T2======start========");
  54. Object value=TestGuaVA.getCache("key","T2");
  55. System.out.println("T2 value=============="+value);
  56. System.out.println("T2======end========");
  57. }
  58. });
  59. Thread t3=new Thread(new Runnable() {
  60. @Override
  61. public void run() {
  62. System.out.println("T3======start========");
  63. Object value=TestGuaVA.getCache("key","T3");
  64. System.out.println("T3 value=============="+value);
  65. System.out.println("T3======end========");
  66. }
  67. });
  68. t1.start();
  69. t2.start();
  70. t3.start();
  71. }
  72. }
public class TestFutureCahe<K,V> {
	private final ConcurrentHashMap<K, Future<V>>  cacheMap=new ConcurrentHashMap<K, Future<V>> (); 
	
	public   Object getCache(K keyValue,String ThreadName){
		Future<V> value=null;
		try{
			System.out.println("ThreadName getCache=============="+ThreadName);
			//从缓存获取数据
			value=cacheMap.get(keyValue);
			//如果没有的话,把数据放到缓存
			if(value==null){
				value= putCache(keyValue,ThreadName);
				return value.get();
			}
			return value.get();
				
		}catch (Exception e) {
		}
		return null;
	}
	
	
	public Future<V> putCache(K keyValue,final String ThreadName){
//		//把数据放到缓存
		Future<V> value=null;
		Callable<V> callable=new Callable<V>() {
				@SuppressWarnings("unchecked")
				@Override
				public V call() throws Exception {
					//可以根据业务从数据库获取等取得数据,这边就模拟已经获取数据了
					System.out.println("ThreadName 执行业务数据并返回处理结果的数据(访问数据库等)=============="+ThreadName);
					return (V) "dataValue";
				}
			};
			FutureTask<V> futureTask=new FutureTask<V>(callable);
			value=cacheMap.putIfAbsent(keyValue, futureTask);
			if(value==null){
				value=futureTask;
				futureTask.run();
			}
		return value;
	}
	
	

	
	
	public static void main(String[] args) {
		final TestFutureCahe<String,String> TestGuaVA=new TestFutureCahe<String,String>();
		
		Thread t1=new Thread(new Runnable() {
			@Override
			public void run() {
				
				System.out.println("T1======start========");
				Object value=TestGuaVA.getCache("key","T1");
				System.out.println("T1 value=============="+value);
				System.out.println("T1======end========");
				
			}
		});
		
		Thread t2=new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println("T2======start========");
				Object value=TestGuaVA.getCache("key","T2");
				System.out.println("T2 value=============="+value);
				System.out.println("T2======end========");
				
			}
		});
		
		Thread t3=new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println("T3======start========");
				Object value=TestGuaVA.getCache("key","T3");
				System.out.println("T3 value=============="+value);
				System.out.println("T3======end========");
				
			}
		});
		
		t1.start();
		t2.start();
		t3.start();

	}

}

线程T1或者线程T2访问cacheMap,如果都没有时,这时执行了FutureTask来完成异步任务,假如线程T1执行了FutureTask,并把保存到ConcurrentHashMap中,通过PutIfAbsent方法,因为putIfAbsent方法如果不存在key对应的值,则将valuekey加入Map,否则返回key对应的旧值。这时线程T2进来时可以获取Future对象,如果没值没关系,这时是对象的引用,等FutureTask执行完,在通过get返回。

我们问题解决了高并发访问缓存的问题,可以回收元素这些,都没有,容易造成内存溢出,Google Guava Cache在这些问题方面都做得挺好的,接下来我们介绍一下。

三.Google Guava Cache的介绍和应用



http://www.java2s.com/Code/Jar/g/Downloadguava1401jar.htm 下载对应的jar包


Guava CacheConcurrentMap很相似,Guava Cache能设置回收,能解决在大数据内存溢出的问题,源代码如下:



public class TestGuaVA<K,V> {
private Cache<K, V> cache= CacheBuilder.newBuilder() .maximumSize(2).expireAfterWrite(10, TimeUnit.MINUTES).build();
public Object getCache(K keyValue,final String ThreadName){
Object value=null;
try {
System.out.println("ThreadName getCache=============="+ThreadName);
//从缓存获取数据
value = cache.get(keyValue, new Callable<V>() {
@SuppressWarnings("unchecked")
public V call() {
System.out.println("ThreadName 执行业务数据并返回处理结果的数据(访问数据库等)=============="+ThreadName);
return (V) "dataValue";
}
});
} catch (ExecutionException e) {
e.printStackTrace();
}
return value;
}





public static void main(String[] args) {
final TestGuaVA<String,String> TestGuaVA=new TestGuaVA<String,String>();


Thread t1=new Thread(new Runnable() {
@Override
public void run() {

System.out.println("T1======start========");
Object value=TestGuaVA.getCache("key","T1");
System.out.println("T1 value=============="+value);
System.out.println("T1======end========");

}
});

Thread t2=new Thread(new Runnable() {
@Override
public void run() {
System.out.println("T2======start========");
Object value=TestGuaVA.getCache("key","T2");
System.out.println("T2 value=============="+value);
System.out.println("T2======end========");

}
});

Thread t3=new Thread(new Runnable() {
@Override
public void run() {
System.out.println("T3======start========");
Object value=TestGuaVA.getCache("key","T3");
System.out.println("T3 value=============="+value);
System.out.println("T3======end========");

}
});

t1.start();
t2.start();
t3.start();


}


}


说明:

CacheBuilder.newBuilder()后面能带一些设置回收的方法:

1maximumSize(long):设置容量大小,超过就开始回收。

2expireAfterAccess(long, TimeUnit):在这个时间段内没有被读/写访问,就会被回收。

3expireAfterWrite(long, TimeUnit):在这个时间段内没有被写访问,就会被回收

(4)removalListener(RemovalListener):监听事件,在元素被删除时,进行监听。

执行结果,如图所示:


  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值