- 浏览: 88683 次
- 性别:
- 来自: 杭州
文章分类
最新评论
-
z694571598:
set 在哪?
js实现Set,Map,List,Queue -
coolboy09:
java并发编程实战上有类似的例子。我有一个疑惑,现在提出来和 ...
高速缓存实现 -
znlyj:
我同样关注,怎么更新缓存?另外,如果我不想将计算结果放到进程内 ...
高速缓存实现 -
Terry_zzz:
很好, 总结的很全面 谢谢!
深入浅出设计模式之单态模式(singleton) -
supertianyi:
好文,学习了
精通js中的Array
各位大虾,本人实现了一个高速缓存,实现方式中依赖java的concurrent包ConcurrendHashMap,贴出代码希望各位能够讨论一下如下的addElement()方法不加锁,会不会出现线程问题(依照本人的理解应该不会,由于本人才疏学浅,还望不吝赐教,另外该方法的实现是参考<<java并发编程实践>>)。
在具体实现中getElement()方法使用了读锁从而支持并发读,在addElement()方法中没有加任何锁依靠FutureTask和实现逻辑来实现线程安全。如上实现优势: 当线程进入addElement方法后,首先判断对应key的Futuretask是否已经存在,如果不存在表明是第一次添加,然后利用Callable和Futuretask来创建一个value对象(如果创建value对象的代价非常昂贵的话,更能体现该实现的优势).假如在第一个线程正在创建value的过程中,addElement方法进入第二个线程,此时第二个线程会首先判断key对应的Futuretask是否已经存在,如不存在则利用Callable和Futuretask来创建一个value对象。试想此时的最好实现就是第二个线程判断对象是否存在或者是否正在创建,如果存在或正在创建那么最好的办法就是等待第一个线程创建完成后直接来分享胜利的果实即可,不需要再进行创建后再依靠concurrentHashMap的putIfAbsent方法来判断是否需要加入Map中,这种实现最明显的优势就是节约了创建一个复杂对象的开销,这一点也是该实现的精华所在.
以上是本人对这个方法的理解,还望各位多多拍砖。。。。。。。。
针对你提出的问题,解释如下:
1.while循环是考虑在Futuretask在创建value过程中可能出现异常导致无法获得对应的value,这里是来做轮询的,只有成功创建了value并返回,从而结束轮询。
2.这样实现addElement的有一个很明显的优势就是:当第二个线程判断key在Map存在,且对应的Futuretask已经存在,那么第二个线程只需要等待创建该key对应的Futuretask的线程返回value,这样如果创建一个对象的性能开销很大的话,这个高速缓存就更能体现出"高速"吧。
1,没错,但是如果一直不被创建的呢?如何保证add线程的个数,采取线程池吗?那么整个线程池如何管理,还是说建立不同的线程池,你这个应该是通用组件,那么通用组件是否应该考虑一下你对整个应用无论是性能还是消耗上都得达到一定标准呢。似乎用阻塞队列都比这个无限循环要好很多吧,当然,可能我误解你的本意了。
2,也就是说你每次只会保存一个缓存,也就是说当我需要获取多个返回的时候也只能一个个等待。是这个意思吗?所以我提出池的概念,当然可能我没理解你这个key,它是否就是类似于spring的beanname一样的一种存在,事实上很多时候我们开销大的原因在于我们需要创建很多很多的这样的对象,而且都是不同的引用,而这些对象紧接着又都会被销毁,这个只要过度作用的对象的创建消耗才不被我们允许。
针对你提出的问题,解释如下:
1.while循环是考虑在Futuretask在创建value过程中可能出现异常导致无法获得对应的value,这里是来做轮询的,只有成功创建了value并返回,从而结束轮询。
2.这样实现addElement的有一个很明显的优势就是:当第二个线程判断key在Map存在,且对应的Futuretask已经存在,那么第二个线程只需要等待创建该key对应的Futuretask的线程返回value,这样如果创建一个对象的性能开销很大的话,这个高速缓存就更能体现出"高速"吧。
你提出的这个问题我个人觉得应该不会出现,因为在put前利用了putIfAbent来保证该key对应的FutureTask是第一次put到Map,如果不是第一次此时只需要等待已经put的FutureTask执行获得value的结果,然后分享胜利果实即可.
的确putIfAbent能够保证只有唯一一个key,不过可惜的是你在调用putIfAbent前,有可能多个线程一起计算同一个key的value
你觉得这个的开销大还是锁的开销大
看来你还没有看明白putIfAbsent()方法之前的代码,那里没有计算value,而是在putIfAbent判断了对应Key的FutureTask不存在后,在又FutureTask去执行创建Value的工作,所以这里没有性能开销。
嗯,是凭映像说的。
其实简单的说,好几个线程new Callable<T> 这种代价可以接受的话,你这样写是没有问题的
如果有异常你循环,那么下次循环还不是异常么?还不如检测出异常放个异常值来的好
还有我调用addElement(final K key, final V value),返回的都是最初计算出来的结果,这个是你想要的么
,换句话说,你的缓存怎么去更新?
这里的实现是没有考虑缓存更新的问题,因为缓存的更新其实是和业务逻辑挂钩的,具体需要根据业务逻辑是定制缓存策略。
你提出的这个问题我个人觉得应该不会出现,因为在put前利用了putIfAbent来保证该key对应的FutureTask是第一次put到Map,如果不是第一次此时只需要等待已经put的FutureTask执行获得value的结果,然后分享胜利果实即可.
的确putIfAbent能够保证只有唯一一个key,不过可惜的是你在调用putIfAbent前,有可能多个线程一起计算同一个key的value
你觉得这个的开销大还是锁的开销大
看来你还没有看明白putIfAbsent()方法之前的代码,那里没有计算value,而是在putIfAbent判断了对应Key的FutureTask不存在后,在又FutureTask去执行创建Value的工作,所以这里没有性能开销。
嗯,是凭映像说的。
其实简单的说,好几个线程new Callable<T> 这种代价可以接受的话,你这样写是没有问题的
如果有异常你循环,那么下次循环还不是异常么?还不如检测出异常放个异常值来的好
还有我调用addElement(final K key, final V value),返回的都是最初计算出来的结果,这个是你想要的么
,换句话说,你的缓存怎么去更新?
你提出的这个问题我个人觉得应该不会出现,因为在put前利用了putIfAbent来保证该key对应的FutureTask是第一次put到Map,如果不是第一次此时只需要等待已经put的FutureTask执行获得value的结果,然后分享胜利果实即可.
的确putIfAbent能够保证只有唯一一个key,不过可惜的是你在调用putIfAbent前,有可能多个线程一起计算同一个key的value
你觉得这个的开销大还是锁的开销大
看来你还没有看明白putIfAbsent()方法之前的代码,那里没有计算value,而是在putIfAbent判断了对应Key的FutureTask不存在后,在又FutureTask去执行创建Value的工作,所以这里没有性能开销。
你提出的这个问题我个人觉得应该不会出现,因为在put前利用了putIfAbent来保证该key对应的FutureTask是第一次put到Map,如果不是第一次此时只需要等待已经put的FutureTask执行获得value的结果,然后分享胜利果实即可.
的确putIfAbent能够保证只有唯一一个key,不过可惜的是你在调用putIfAbent前,有可能多个线程一起计算同一个key的value
你觉得这个的开销大还是锁的开销大
你提出的这个问题我个人觉得应该不会出现,因为在put前利用了putIfAbent来保证该key对应的FutureTask是第一次put到Map,如果不是第一次此时只需要等待已经put的FutureTask执行获得value的结果,然后分享胜利果实即可.
同意kazy的观点,在多个线程同时进入到null里的时候,eval 和future 可能会被创建很多个。
另外又看了一下putIfAbsent方法,大概意思是
如果这样的话,楼主首先使用有锁的map将future放入,保证此时map的future唯一,如果返回值是null说明是该线程首次放入,然后future做生成真正value的操作,这样感觉应该是可以的。
不过这样的话,是否一次就可以搞定value的值,而不需要while(true)了?
因为在利用FutureTask生成Value的过程中,有可能出现异常,所以这里需要while(true)来做轮询,知道对应key有一个对应的FutureTask放入Map中。
当然这里没有考虑到cache的修改问题,在实际应用中cache的修改问题是和业务场景挂钩的,也就所说的缓存策略的问题。
这个要顶。不知道LZ的需求会不会break这一条
同意kazy的观点,在多个线程同时进入到null里的时候,eval 和future 可能会被创建很多个。
另外又看了一下putIfAbsent方法,大概意思是
如果这样的话,楼主首先使用有锁的map将future放入,保证此时map的future唯一,如果返回值是null说明是该线程首次放入,然后future做生成真正value的操作,这样感觉应该是可以的。
不过这样的话,是否一次就可以搞定value的值,而不需要while(true)了?
getElement()的确不需要锁!
关于你提出的问题,入下说明:
1:读写锁在此吃是应该取消,因为concurrentHashMap中已经是线程安全的了。
2:putIfAbent方法防止了同时写的问题!这里主要是想说明即便此时出现了多线程的情况,当第二个线程来请求获得对应key的value时,只要发现已经有第一个线程在创建对应value后只需要等待第一个线程的结构就行了,这也就是为什么要使用Futuretask的目的,这样就可以很好的避免在创建比较消耗性能的value对象的性能损失.
3:while(true)的目的是实现轮询,如果在Futuretak执行创建value失败时轮询做该操作,知道成功推出。
4:至于将addElement方法中的value改Callable的实现,这一点只是实现方式的问题吧。
public class Cache<K, V> { private final ConcurrentHashMap<K, FutureTask<V>> cache = new ConcurrentHashMap<K, FutureTask<V>>(); private final ReadWriteLock lock = new ReentrantReadWriteLock(); private final Lock readLock = lock.readLock(); // private final Lock writeLock = lock.writeLock(); public V addElement(final K key, final V value) { FutureTask<V> f = cache.get(key); while (true) { if (null == f) { Callable<V> eval = new Callable<V>() { @Override public V call() throws Exception { //此处实现比较简单,但是如果创建一个V的对象需要比较消耗性能的话, //这种缓存实现就有明显的优势 return value; } }; FutureTask<V> future = new FutureTask<V>(eval); f = cache.putIfAbsent(key, future); if (null == f) { f = future; future.run(); } } try { return f.get(); } catch (Exception e) { e.printStackTrace(); } } } public V getElement(K key) { try { readLock.lock(); return cache.get(key).get(); } catch (Exception e) { e.printStackTrace(); } finally { readLock.unlock(); } return null; } }
在具体实现中getElement()方法使用了读锁从而支持并发读,在addElement()方法中没有加任何锁依靠FutureTask和实现逻辑来实现线程安全。如上实现优势: 当线程进入addElement方法后,首先判断对应key的Futuretask是否已经存在,如果不存在表明是第一次添加,然后利用Callable和Futuretask来创建一个value对象(如果创建value对象的代价非常昂贵的话,更能体现该实现的优势).假如在第一个线程正在创建value的过程中,addElement方法进入第二个线程,此时第二个线程会首先判断key对应的Futuretask是否已经存在,如不存在则利用Callable和Futuretask来创建一个value对象。试想此时的最好实现就是第二个线程判断对象是否存在或者是否正在创建,如果存在或正在创建那么最好的办法就是等待第一个线程创建完成后直接来分享胜利的果实即可,不需要再进行创建后再依靠concurrentHashMap的putIfAbsent方法来判断是否需要加入Map中,这种实现最明显的优势就是节约了创建一个复杂对象的开销,这一点也是该实现的精华所在.
以上是本人对这个方法的理解,还望各位多多拍砖。。。。。。。。
评论
27 楼
coolboy09
2014-11-25
java并发编程实战上有类似的例子。我有一个疑惑,现在提出来和博主讨论:f = cache.putIfAbsent(key, future);这一操作确实是原子操作,假设:第一个线程在执行完这个操作后,被CPU调度暂停,此时第二个再执行时,发现第一个已经创建,于是第二个线程将直接返回第一个线程创建的这个FutureTask对象,这没问题。依据代码,如果此时第一个线程仍然没有获得执行余下代码的机会,也就是博主代码的第26-29行,此时,第二个线程直接执行到第33行(return f.get()),而不会执行26-29行,那这个最后的结果不就是null了吗?因为,26-29行没有执行,所以,future没有run。
26 楼
znlyj
2014-08-25
我同样关注,怎么更新缓存?
另外,如果我不想将计算结果放到进程内,想放到redis,怎么操作?
另外,如果我不想将计算结果放到进程内,想放到redis,怎么操作?
25 楼
sunson468
2010-06-10
soongbo 写道
sunson468 写道
这个是不是对象产生器~~~不是所谓的缓存吧
你那个判断什么的是不是写反了啊,怎么先循环再判断是否为空啊,而且add操作是可以并发的,但是你的key-value是一对一的啊,这样的等待就一点都不高速了啊~~
可以考虑修改一下,变成一种对象池,缺少了就补充这样还实用点,事实上我们有某个项目就打算采取这个滴
你那个判断什么的是不是写反了啊,怎么先循环再判断是否为空啊,而且add操作是可以并发的,但是你的key-value是一对一的啊,这样的等待就一点都不高速了啊~~
可以考虑修改一下,变成一种对象池,缺少了就补充这样还实用点,事实上我们有某个项目就打算采取这个滴
针对你提出的问题,解释如下:
1.while循环是考虑在Futuretask在创建value过程中可能出现异常导致无法获得对应的value,这里是来做轮询的,只有成功创建了value并返回,从而结束轮询。
2.这样实现addElement的有一个很明显的优势就是:当第二个线程判断key在Map存在,且对应的Futuretask已经存在,那么第二个线程只需要等待创建该key对应的Futuretask的线程返回value,这样如果创建一个对象的性能开销很大的话,这个高速缓存就更能体现出"高速"吧。
1,没错,但是如果一直不被创建的呢?如何保证add线程的个数,采取线程池吗?那么整个线程池如何管理,还是说建立不同的线程池,你这个应该是通用组件,那么通用组件是否应该考虑一下你对整个应用无论是性能还是消耗上都得达到一定标准呢。似乎用阻塞队列都比这个无限循环要好很多吧,当然,可能我误解你的本意了。
2,也就是说你每次只会保存一个缓存,也就是说当我需要获取多个返回的时候也只能一个个等待。是这个意思吗?所以我提出池的概念,当然可能我没理解你这个key,它是否就是类似于spring的beanname一样的一种存在,事实上很多时候我们开销大的原因在于我们需要创建很多很多的这样的对象,而且都是不同的引用,而这些对象紧接着又都会被销毁,这个只要过度作用的对象的创建消耗才不被我们允许。
24 楼
soongbo
2010-06-09
sunson468 写道
这个是不是对象产生器~~~不是所谓的缓存吧
你那个判断什么的是不是写反了啊,怎么先循环再判断是否为空啊,而且add操作是可以并发的,但是你的key-value是一对一的啊,这样的等待就一点都不高速了啊~~
可以考虑修改一下,变成一种对象池,缺少了就补充这样还实用点,事实上我们有某个项目就打算采取这个滴
你那个判断什么的是不是写反了啊,怎么先循环再判断是否为空啊,而且add操作是可以并发的,但是你的key-value是一对一的啊,这样的等待就一点都不高速了啊~~
可以考虑修改一下,变成一种对象池,缺少了就补充这样还实用点,事实上我们有某个项目就打算采取这个滴
针对你提出的问题,解释如下:
1.while循环是考虑在Futuretask在创建value过程中可能出现异常导致无法获得对应的value,这里是来做轮询的,只有成功创建了value并返回,从而结束轮询。
2.这样实现addElement的有一个很明显的优势就是:当第二个线程判断key在Map存在,且对应的Futuretask已经存在,那么第二个线程只需要等待创建该key对应的Futuretask的线程返回value,这样如果创建一个对象的性能开销很大的话,这个高速缓存就更能体现出"高速"吧。
23 楼
sunson468
2010-06-09
这个是不是对象产生器~~~不是所谓的缓存吧
你那个判断什么的是不是写反了啊,怎么先循环再判断是否为空啊,而且add操作是可以并发的,但是你的key-value是一对一的啊,这样的等待就一点都不高速了啊~~
可以考虑修改一下,变成一种对象池,缺少了就补充这样还实用点,事实上我们有某个项目就打算采取这个滴
你那个判断什么的是不是写反了啊,怎么先循环再判断是否为空啊,而且add操作是可以并发的,但是你的key-value是一对一的啊,这样的等待就一点都不高速了啊~~
可以考虑修改一下,变成一种对象池,缺少了就补充这样还实用点,事实上我们有某个项目就打算采取这个滴
22 楼
soongbo
2010-06-09
beneo 写道
soongbo 写道
beneo 写道
soongbo 写道
kazy 写道
从另外的地方抄个例子过来,
如果你只调用get(),或只调用put()时,ConcurrentHashMap是线程安全的。
但是,在你调用完get后,调用put之前,
如果有另外一个线程调用了map.put(name, x),
你再去执行map.put(name,x),
就很可能把前面的操作结果覆盖掉了。
所以,即使在线程安全的情况下,
你还是有可能违反原子操作的规则。
ConcurrentHashMap<String,String> map; String getString(String name) { String x = map.get(name); if (x == null) { x = new String(); map.put(name, x); } return x; }
如果你只调用get(),或只调用put()时,ConcurrentHashMap是线程安全的。
但是,在你调用完get后,调用put之前,
如果有另外一个线程调用了map.put(name, x),
你再去执行map.put(name,x),
就很可能把前面的操作结果覆盖掉了。
所以,即使在线程安全的情况下,
你还是有可能违反原子操作的规则。
你提出的这个问题我个人觉得应该不会出现,因为在put前利用了putIfAbent来保证该key对应的FutureTask是第一次put到Map,如果不是第一次此时只需要等待已经put的FutureTask执行获得value的结果,然后分享胜利果实即可.
的确putIfAbent能够保证只有唯一一个key,不过可惜的是你在调用putIfAbent前,有可能多个线程一起计算同一个key的value
你觉得这个的开销大还是锁的开销大
看来你还没有看明白putIfAbsent()方法之前的代码,那里没有计算value,而是在putIfAbent判断了对应Key的FutureTask不存在后,在又FutureTask去执行创建Value的工作,所以这里没有性能开销。
嗯,是凭映像说的。
其实简单的说,好几个线程new Callable<T> 这种代价可以接受的话,你这样写是没有问题的
引用
因为在利用FutureTask生成Value的过程中,有可能出现异常,所以这里需要while(true)来做轮询,知道对应key有一个对应的FutureTask放入Map中。
如果有异常你循环,那么下次循环还不是异常么?还不如检测出异常放个异常值来的好
还有我调用addElement(final K key, final V value),返回的都是最初计算出来的结果,这个是你想要的么
,换句话说,你的缓存怎么去更新?
这里的实现是没有考虑缓存更新的问题,因为缓存的更新其实是和业务逻辑挂钩的,具体需要根据业务逻辑是定制缓存策略。
21 楼
beneo
2010-06-09
soongbo 写道
beneo 写道
soongbo 写道
kazy 写道
从另外的地方抄个例子过来,
如果你只调用get(),或只调用put()时,ConcurrentHashMap是线程安全的。
但是,在你调用完get后,调用put之前,
如果有另外一个线程调用了map.put(name, x),
你再去执行map.put(name,x),
就很可能把前面的操作结果覆盖掉了。
所以,即使在线程安全的情况下,
你还是有可能违反原子操作的规则。
ConcurrentHashMap<String,String> map; String getString(String name) { String x = map.get(name); if (x == null) { x = new String(); map.put(name, x); } return x; }
如果你只调用get(),或只调用put()时,ConcurrentHashMap是线程安全的。
但是,在你调用完get后,调用put之前,
如果有另外一个线程调用了map.put(name, x),
你再去执行map.put(name,x),
就很可能把前面的操作结果覆盖掉了。
所以,即使在线程安全的情况下,
你还是有可能违反原子操作的规则。
你提出的这个问题我个人觉得应该不会出现,因为在put前利用了putIfAbent来保证该key对应的FutureTask是第一次put到Map,如果不是第一次此时只需要等待已经put的FutureTask执行获得value的结果,然后分享胜利果实即可.
的确putIfAbent能够保证只有唯一一个key,不过可惜的是你在调用putIfAbent前,有可能多个线程一起计算同一个key的value
你觉得这个的开销大还是锁的开销大
看来你还没有看明白putIfAbsent()方法之前的代码,那里没有计算value,而是在putIfAbent判断了对应Key的FutureTask不存在后,在又FutureTask去执行创建Value的工作,所以这里没有性能开销。
嗯,是凭映像说的。
其实简单的说,好几个线程new Callable<T> 这种代价可以接受的话,你这样写是没有问题的
引用
因为在利用FutureTask生成Value的过程中,有可能出现异常,所以这里需要while(true)来做轮询,知道对应key有一个对应的FutureTask放入Map中。
如果有异常你循环,那么下次循环还不是异常么?还不如检测出异常放个异常值来的好
还有我调用addElement(final K key, final V value),返回的都是最初计算出来的结果,这个是你想要的么
,换句话说,你的缓存怎么去更新?
20 楼
soongbo
2010-06-09
beneo 写道
soongbo 写道
kazy 写道
从另外的地方抄个例子过来,
如果你只调用get(),或只调用put()时,ConcurrentHashMap是线程安全的。
但是,在你调用完get后,调用put之前,
如果有另外一个线程调用了map.put(name, x),
你再去执行map.put(name,x),
就很可能把前面的操作结果覆盖掉了。
所以,即使在线程安全的情况下,
你还是有可能违反原子操作的规则。
ConcurrentHashMap<String,String> map; String getString(String name) { String x = map.get(name); if (x == null) { x = new String(); map.put(name, x); } return x; }
如果你只调用get(),或只调用put()时,ConcurrentHashMap是线程安全的。
但是,在你调用完get后,调用put之前,
如果有另外一个线程调用了map.put(name, x),
你再去执行map.put(name,x),
就很可能把前面的操作结果覆盖掉了。
所以,即使在线程安全的情况下,
你还是有可能违反原子操作的规则。
你提出的这个问题我个人觉得应该不会出现,因为在put前利用了putIfAbent来保证该key对应的FutureTask是第一次put到Map,如果不是第一次此时只需要等待已经put的FutureTask执行获得value的结果,然后分享胜利果实即可.
的确putIfAbent能够保证只有唯一一个key,不过可惜的是你在调用putIfAbent前,有可能多个线程一起计算同一个key的value
你觉得这个的开销大还是锁的开销大
看来你还没有看明白putIfAbsent()方法之前的代码,那里没有计算value,而是在putIfAbent判断了对应Key的FutureTask不存在后,在又FutureTask去执行创建Value的工作,所以这里没有性能开销。
19 楼
beneo
2010-06-09
soongbo 写道
kazy 写道
从另外的地方抄个例子过来,
如果你只调用get(),或只调用put()时,ConcurrentHashMap是线程安全的。
但是,在你调用完get后,调用put之前,
如果有另外一个线程调用了map.put(name, x),
你再去执行map.put(name,x),
就很可能把前面的操作结果覆盖掉了。
所以,即使在线程安全的情况下,
你还是有可能违反原子操作的规则。
ConcurrentHashMap<String,String> map; String getString(String name) { String x = map.get(name); if (x == null) { x = new String(); map.put(name, x); } return x; }
如果你只调用get(),或只调用put()时,ConcurrentHashMap是线程安全的。
但是,在你调用完get后,调用put之前,
如果有另外一个线程调用了map.put(name, x),
你再去执行map.put(name,x),
就很可能把前面的操作结果覆盖掉了。
所以,即使在线程安全的情况下,
你还是有可能违反原子操作的规则。
你提出的这个问题我个人觉得应该不会出现,因为在put前利用了putIfAbent来保证该key对应的FutureTask是第一次put到Map,如果不是第一次此时只需要等待已经put的FutureTask执行获得value的结果,然后分享胜利果实即可.
的确putIfAbent能够保证只有唯一一个key,不过可惜的是你在调用putIfAbent前,有可能多个线程一起计算同一个key的value
你觉得这个的开销大还是锁的开销大
18 楼
soongbo
2010-06-09
kazy 写道
从另外的地方抄个例子过来,
如果你只调用get(),或只调用put()时,ConcurrentHashMap是线程安全的。
但是,在你调用完get后,调用put之前,
如果有另外一个线程调用了map.put(name, x),
你再去执行map.put(name,x),
就很可能把前面的操作结果覆盖掉了。
所以,即使在线程安全的情况下,
你还是有可能违反原子操作的规则。
ConcurrentHashMap<String,String> map; String getString(String name) { String x = map.get(name); if (x == null) { x = new String(); map.put(name, x); } return x; }
如果你只调用get(),或只调用put()时,ConcurrentHashMap是线程安全的。
但是,在你调用完get后,调用put之前,
如果有另外一个线程调用了map.put(name, x),
你再去执行map.put(name,x),
就很可能把前面的操作结果覆盖掉了。
所以,即使在线程安全的情况下,
你还是有可能违反原子操作的规则。
你提出的这个问题我个人觉得应该不会出现,因为在put前利用了putIfAbent来保证该key对应的FutureTask是第一次put到Map,如果不是第一次此时只需要等待已经put的FutureTask执行获得value的结果,然后分享胜利果实即可.
17 楼
soongbo
2010-06-09
hankesi2000 写道
kazy 写道
从另外的地方抄个例子过来,
如果你只调用get(),或只调用put()时,ConcurrentHashMap是线程安全的。
但是,在你调用完get后,调用put之前,
如果有另外一个线程调用了map.put(name, x),
你再去执行map.put(name,x),
就很可能把前面的操作结果覆盖掉了。
所以,即使在线程安全的情况下,
你还是有可能违反原子操作的规则。
ConcurrentHashMap<String,String> map; String getString(String name) { String x = map.get(name); if (x == null) { x = new String(); map.put(name, x); } return x; }
如果你只调用get(),或只调用put()时,ConcurrentHashMap是线程安全的。
但是,在你调用完get后,调用put之前,
如果有另外一个线程调用了map.put(name, x),
你再去执行map.put(name,x),
就很可能把前面的操作结果覆盖掉了。
所以,即使在线程安全的情况下,
你还是有可能违反原子操作的规则。
同意kazy的观点,在多个线程同时进入到null里的时候,eval 和future 可能会被创建很多个。
另外又看了一下putIfAbsent方法,大概意思是
if (!map.containsKey(key)) return map.put(key, value); else return map.get(key);
如果这样的话,楼主首先使用有锁的map将future放入,保证此时map的future唯一,如果返回值是null说明是该线程首次放入,然后future做生成真正value的操作,这样感觉应该是可以的。
不过这样的话,是否一次就可以搞定value的值,而不需要while(true)了?
因为在利用FutureTask生成Value的过程中,有可能出现异常,所以这里需要while(true)来做轮询,知道对应key有一个对应的FutureTask放入Map中。
16 楼
soongbo
2010-06-09
archerfrank 写道
关键点是LZ的cache是不能修改的吧。一旦put进去,就不会改了,其他线程只能分享胜利成果了。
所以线程安全了。
所以线程安全了。
当然这里没有考虑到cache的修改问题,在实际应用中cache的修改问题是和业务场景挂钩的,也就所说的缓存策略的问题。
15 楼
archerfrank
2010-06-09
kazy 写道
从另外的地方抄个例子过来,
如果你只调用get(),或只调用put()时,ConcurrentHashMap是线程安全的。
但是,在你调用完get后,调用put之前,
如果有另外一个线程调用了map.put(name, x),
你再去执行map.put(name,x),
就很可能把前面的操作结果覆盖掉了。
所以,即使在线程安全的情况下,
你还是有可能违反原子操作的规则。
ConcurrentHashMap<String,String> map; String getString(String name) { String x = map.get(name); if (x == null) { x = new String(); map.put(name, x); } return x; }
如果你只调用get(),或只调用put()时,ConcurrentHashMap是线程安全的。
但是,在你调用完get后,调用put之前,
如果有另外一个线程调用了map.put(name, x),
你再去执行map.put(name,x),
就很可能把前面的操作结果覆盖掉了。
所以,即使在线程安全的情况下,
你还是有可能违反原子操作的规则。
这个要顶。不知道LZ的需求会不会break这一条
14 楼
archerfrank
2010-06-09
关键点是LZ的cache是不能修改的吧。一旦put进去,就不会改了,其他线程只能分享胜利成果了。
所以线程安全了。
所以线程安全了。
13 楼
hankesi2000
2010-06-09
kazy 写道
从另外的地方抄个例子过来,
如果你只调用get(),或只调用put()时,ConcurrentHashMap是线程安全的。
但是,在你调用完get后,调用put之前,
如果有另外一个线程调用了map.put(name, x),
你再去执行map.put(name,x),
就很可能把前面的操作结果覆盖掉了。
所以,即使在线程安全的情况下,
你还是有可能违反原子操作的规则。
ConcurrentHashMap<String,String> map; String getString(String name) { String x = map.get(name); if (x == null) { x = new String(); map.put(name, x); } return x; }
如果你只调用get(),或只调用put()时,ConcurrentHashMap是线程安全的。
但是,在你调用完get后,调用put之前,
如果有另外一个线程调用了map.put(name, x),
你再去执行map.put(name,x),
就很可能把前面的操作结果覆盖掉了。
所以,即使在线程安全的情况下,
你还是有可能违反原子操作的规则。
同意kazy的观点,在多个线程同时进入到null里的时候,eval 和future 可能会被创建很多个。
另外又看了一下putIfAbsent方法,大概意思是
if (!map.containsKey(key)) return map.put(key, value); else return map.get(key);
如果这样的话,楼主首先使用有锁的map将future放入,保证此时map的future唯一,如果返回值是null说明是该线程首次放入,然后future做生成真正value的操作,这样感觉应该是可以的。
不过这样的话,是否一次就可以搞定value的值,而不需要while(true)了?
12 楼
soongbo
2010-06-08
wkoffee说:"你addElement的原子性是基于putIfAbsent是原子的,所以你还是用了concurrentHashMap内部的锁了,没看出什么特别有价值的地方".
说明如下:这里主要是想说明即便此时出现了多线程的情况,当第二个线程来请求获得对应key的value时,只要发现已经有第一个线程在创建对应value后,只需要等待第一个线程的结果就行了,这也就是为什么要使用Futuretask的目的,这样就可以很好的避免在创建比较消耗性能的value对象的性能损失.
说明如下:这里主要是想说明即便此时出现了多线程的情况,当第二个线程来请求获得对应key的value时,只要发现已经有第一个线程在创建对应value后,只需要等待第一个线程的结果就行了,这也就是为什么要使用Futuretask的目的,这样就可以很好的避免在创建比较消耗性能的value对象的性能损失.
11 楼
soongbo
2010-06-08
tyzqqq 写道
getElement没必要加线程锁吧,它仅仅是取值
getElement()的确不需要锁!
10 楼
wkoffee
2010-06-08
你addElement的原子性是基于putIfAbsent是原子的,所以你还是用了concurrentHashMap内部的锁了,没看出什么特别有价值的地方
9 楼
tyzqqq
2010-06-08
getElement没必要加线程锁吧,它仅仅是取值
8 楼
soongbo
2010-06-08
wantofly_gj 写道
缓存么,就是要复用.你这里就是要复用每个key对应的value值,而你在Map中使用FutureTask而不是直接存value,只是为了占位(避免两个线程同时检查到key不存在,然后同时创建两个value).呵呵.我看了半天才明白:)
问题1:读写再入锁是没用的,putIfAbsent 方法其实就已经实现了防止同时写入的可能了(第一个线程占位之后,第二个线程就不可能再写入了).
问题2: while(true)实在没看明白??
问题3:之前beneo 说的 addElement 这个方法接口上面,final V value 应该改成 Callable<V> callable吧? 你传入的value值转了一圈就被直接返回了,没用啊
问题1:读写再入锁是没用的,putIfAbsent 方法其实就已经实现了防止同时写入的可能了(第一个线程占位之后,第二个线程就不可能再写入了).
问题2: while(true)实在没看明白??
问题3:之前beneo 说的 addElement 这个方法接口上面,final V value 应该改成 Callable<V> callable吧? 你传入的value值转了一圈就被直接返回了,没用啊
关于你提出的问题,入下说明:
1:读写锁在此吃是应该取消,因为concurrentHashMap中已经是线程安全的了。
2:putIfAbent方法防止了同时写的问题!这里主要是想说明即便此时出现了多线程的情况,当第二个线程来请求获得对应key的value时,只要发现已经有第一个线程在创建对应value后只需要等待第一个线程的结构就行了,这也就是为什么要使用Futuretask的目的,这样就可以很好的避免在创建比较消耗性能的value对象的性能损失.
3:while(true)的目的是实现轮询,如果在Futuretak执行创建value失败时轮询做该操作,知道成功推出。
4:至于将addElement方法中的value改Callable的实现,这一点只是实现方式的问题吧。
发表评论
-
Maven2 POM.xml 配置元素详解
2011-06-16 08:56 1422<!--可以免费转载,转载时请注明出处 http:// ... -
深入浅出URL编码
2010-12-24 09:35 950一、问题: 编码问题是JAVA初学者在web开 ... -
mysql自动关闭服务、连接限制等问题的解决方法
2010-11-22 09:36 3039通过mysql服务器端程序mysql Administrato ... -
Java开发中文件上传应用
2010-05-15 23:54 1165今天闲暇无所事事,很长时间没有写博客了,突然想起很多开发 ... -
Java ClassLoader 详解
2010-03-23 12:33 1102类加载器是 Java 语言的一个创新,也是 Java 语言流行 ... -
Java垃圾回收机制和性能调优
2010-01-29 12:40 8801.JVM的gc概述 g ... -
Java编码问题
2009-11-25 16:36 744乱码对于使用非英语文 ... -
JDK+Tomcat +eclipse+MyEclipse的配置
2009-11-16 14:48 1191说一下关于JDK+Tomcat +ecli ... -
Java图片水印处理
2009-10-30 13:28 1556import java.awt.AlphaComposite; ... -
Java正则表达式的解释说明
2009-10-28 18:18 717表达式意义: 1.字符 x ... -
Freemarker语法
2009-10-21 15:29 792常用语法 EG.一个对象BOOK 1.输出 $…{book.n ... -
Servlet的两种跳转区别
2009-10-19 13:46 1288在servlet中,一般跳转都 ... -
Berkeley DB实例
2009-10-15 09:49 1339package test; import com.sleep ... -
Berkeley DB使用说明
2009-10-14 18:32 1780关键字: berkeley db java edi ... -
Tomcat配置详解
2009-09-29 11:44 970第一步:下载j2sdk和tomcat 到sun官方站(htt ... -
URLConnection的连接、超时、关闭用法总结
2009-09-10 12:57 1798ava中可以使用HttpURLConnection来请求WEB ... -
时间操作工具类
2009-09-09 14:27 3214在项目开发工程中,使用了很多时间的操作,改工具类提供了很 ... -
Java操作图片改变大小加水印
2009-09-09 11:21 2071在实际的项目开发中,有可能遇到对图片的操作,比如加水印, ... -
操作抓取网络资源
2009-09-08 12:57 861在实际开发过程中,大家难免遇到抓取网络资源的操作,列如:抓 ... -
java中操作Excel
2009-09-08 12:49 1061在企业级开发中,经常遇到读写excel的操作,在此将一些 ...
相关推荐
并发访问控制是高速缓存实现中的另一大挑战。在Java中,可以使用synchronized关键字或者java.util.concurrent包中的工具类如ReentrantLock、Semaphore等来实现线程安全。例如,当多个线程同时尝试读写缓存时,需要...
下面是一个简单的示例程序,展示了如何使用高速缓存实现文件读写。 首先,我们需要建立两个文件句柄,一个用于读取源文件,另一个用于写入目标文件。然后,我们可以使用 ReadFile 函数读取源文件的内容,并将其写入...
操作系统实验之第二高速缓存方式实现文件读写操作。
正确的高速缓存实现可以大大提高系统性能。 高速缓存是计算机系统结构中的一个关键组件,它可以大大提高系统性能。通过了解高速缓存的原理、结构和实现,我们可以更好地设计和实现高速缓存,以提高系统性能。
高速缓存调度问题的C++实现代码,采用最优的opt方法。代码注释详实,可读性好。
该工程包含数据缓存D_Cache和指令缓存I_Cache的Verilog代码和仿真文件,Cache的详细技术参数包含在.v文件的注释中。 直接相连16KB D_Cache Cache写策略: 写回法+写分配 (二路)组相连16KB I_Cache Cache替换策略: ...
它通过一组例程和数据结构来实现数据的快速访问,包括读取和写入文件数据到内存中的高速缓存区。 计算系统高速缓存的大小是高速缓存管理的关键部分。高速缓存的大小可以是虚拟的也可以是物理的。虚拟大小指的是缓存...
在多处理器系统中,处理器之间通过高速缓存实现通信。高速缓存的一致性问题是多处理器系统设计中的一个重要挑战。为了在访问时间上与高速的处理器相匹配,多处理器系统要使用高速缓存。高速缓存能够提高处理器对...
**高速缓存**(Cache)是一种特殊类型的存储器子系统,它通过复制频繁使用的数据来实现快速访问的目的。Cache通常采用比主内存(RAM)更快但成本更高的静态随机存取存储器(SRAM)作为物理介质。 #### Cache的基本...
本项目通过VB6(Visual Basic 6)编程环境,结合Access2003数据库,实现了利用高速缓存调度算法对通讯信息进行缓冲,以提高数据传输效率和系统响应速度。以下是关于这一主题的详细知识解释。 首先,高速缓存(Cache...
数据高速缓存区命中率是衡量数据库性能的关键指标之一,特别是在高并发的环境中,缓存的效率直接影响到系统的响应速度和资源利用率。本文档将深入探讨数据高速缓存区(Buffer Cache)的管理与优化策略,以提升其命中...
21065L的并行多通道数据采集板上高速采样缓存的设计与电路结构,给出了采用FPGA实现通道复用和采样数据预处理,从而构造16MB的SDRAM海量缓存以将高速缓存中的多批次采样数据经AD-21065L倒入SDRAM存储的实现方法。...
该工程包含数据缓存D_Cache和指令缓存I_Cache的Verilog代码和仿真文件,附带可运行的ISE工程文件,Cache的详细技术参数包含在.v文件的注释中。 直接相连16KB D_Cache Cache写策略: 写回法+写分配 (二路)组相连16KB ...
综上所述,通过web.config和Application对象构建的高速缓存机制,可以在.NET应用程序中实现对全局共享数据的有效管理和高速访问,从而提高应用程序的运行效率。不过,也需要合理规划缓存策略,避免造成资源的浪费或...
21065L的并行多通道数据采集板上高速采样缓存的设计与电路结构,给出了采用FPGA实现通道复用和采样数据预处理,从而构造16MB的SDRAM海量缓存以将高速缓存中的多批次采样数据经AD-21065L倒入SDRAM存储的实现方法。...
高速数据采集系统中高速缓存与海量缓存的实现可用.pdf
第一部分“高速缓存存储系统”介绍了高速缓存体系结构、术语和概念,详细考察了4种常见的高速缓存实现——3种虚拟高速缓存的变体和物理高速缓存。第二部分“多处理机系统”讨论了调整单处理机内核的实现,使之适合于...
### vxWorks下的高速缓存存储器一致性问题解决方案 #### 概述 在嵌入式系统开发领域,实时操作系统(RTOS)的应用极为广泛。其中,**Wind River Systems** 公司的 **vxWorks** 是目前市场上最先进且应用广泛的实时...