ConcurrentHashMap的使用

缓存的使用

  • 高性能本地缓存:对系统中常用到的业务数据放到缓存中以提高系统性能,限制是单服务器模式
  • 分布式缓存:常用分布式缓存技术memcached、redis等

ConcurrentHashMap就是常用的高并发下的缓存对象。关于ConcurrentHashMap的原理和一些与HashTable等的对比可以参照:

http://ifeve.com/concurrenthashmap/

 

接下来直接上例子:

/**
 * @author longe
 * 
 */
public class ConcurrentMapTest {

	public static ConcurrentMap<String, Future<String>> cMap = new ConcurrentHashMap<>();
	public static ConcurrentHashMap<String, String> cMap2 = new ConcurrentHashMap<>();
	public static int index = 0;

	public static void main(String[] args) {
		for (int i = 0; i < 5; i++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					concurrentMap2("3");
					try {
						concurrentMap("123");
					} catch (InterruptedException | ExecutionException | TimeoutException e) {
						e.printStackTrace();
					}
				}
			}).start();
		}
	}

	/**
	 * 解决并发写的线程安全问题。但是高并发可读取会造成重复写的问题...如果put的业务计算复杂将耗费不必要的资源
	 * 解决缓存读取问题,但可能会出现缓存重复写
	 * 
	 * @param key
	 */
	private static void concurrentMap2(String key) {
		System.out.println(Thread.currentThread().getName() + " start ....");
		String f = cMap2.get(key);
		// ConcurrentMap读不加锁,写加锁。当并发量高时会出现重复compute的操作,然后才put到map中
		if (f == null) {
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			cMap2.put(key, "dataTest" + index);
			System.out.println(Thread.currentThread().getName() + " compute , index================== " + index++);
		}
		System.out.println(Thread.currentThread().getName() + " end .... " + cMap2.get(key));

	}

	/**
	 * 解决并发写问题,同时避免了重复put计算的问题 解决缓存读写的问题
	 * 
	 * @param key
	 * @throws InterruptedException
	 * @throws ExecutionException
	 * @throws TimeoutException
	 */
	private static void concurrentMap(String key) throws InterruptedException, ExecutionException, TimeoutException {
		System.out.println(Thread.currentThread().getName() + " start ....");
		Future<String> f = null;
		f = cMap.get(key);
		if (f == null) {
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			FutureTask<String> fTask = new FutureTask<String>(new Callable<String>() {
				public String call() throws Exception {

					try {
						Thread.sleep(2000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + " compute , index=============== " + index++);
					return "456789123";
				}
			});
			f = cMap.putIfAbsent(key, fTask); // 相当于get-if-absent-compute,而且是原子执行,解决了并发读的问题。(FutureTask解决compute步骤)
			if (f == null) {
				f = fTask;
				// f的值是FutureTask对象引用,解决了call的重复调用问题,只用一个线程会执行run()方法
				fTask.run();
			}
		}
		System.out.println("end ===========");
		// get会等待FutureTask的计算结果,可以设置等待超时事件,超时会抛出超时异常
		System.out.println(Thread.currentThread().getName() + " end ....====== " + f.get(3000, TimeUnit.MILLISECONDS));
		// get会等待FutureTask的计算结果,永久等待
		System.out.println(Thread.currentThread().getName() + " end .... " + f.get());
	}

}

 

concurrentMap2的执行结果
Thread-0 start ....
Thread-2 start ....
Thread-4 start ....
Thread-1 start ....
Thread-3 start ....
Thread-4 compute , index================== 0
Thread-0 compute , index================== 2
Thread-0 end .... dataTest0
Thread-2 compute , index================== 1
Thread-2 end .... dataTest0
Thread-4 end .... dataTest0
Thread-3 compute , index================== 3
Thread-1 compute , index================== 4
Thread-1 end .... dataTest3
Thread-3 end .... dataTest3

 可以看到put方法被重复执行....

concurrentMap的执行结果
Thread-1 start ....
Thread-3 start ....
Thread-0 start ....
Thread-2 start ....
Thread-4 start ....
end ===========
end ===========
end ===========
end ===========
Thread-1 compute , index=============== 0
Thread-3 end ....====== 456789123
Thread-3 end .... 456789123
end ===========
Thread-1 end ....====== 456789123
Thread-1 end .... 456789123
Thread-0 end ....====== 456789123
Thread-0 end .... 456789123
Thread-2 end ....====== 456789123
Thread-2 end .... 456789123
Thread-4 end ....====== 456789123
Thread-4 end .... 456789123

可以看到put运算只执行一次....

总结:

  1. 如果缓存对象可以在系统启动时进行初始化加载,可以不使用ConcurrentHashMap
  2. 如果缓存在put时计算比较复杂,那么推荐直接使用concurrentMap写法
  3. ConcurrentHashMap缺陷就是缓存无法回收,导致内存溢出问题。此问题在google发布Guava的Cache很好的进行了处理,可查看另一篇文章Guava Cache的使用

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值