在整数值上同步
可能重复:
在java中增加锁的数量的最佳方法是什么?
假设我想locking一个整数id值。 在这种情况下,有一个函数从caching中提取值,如果值不存在,则执行相当昂贵的检索/存储。
现有代码不同步,可能会触发多个检索/存储操作:
//psuedocode public Page getPage (Integer id){ Page p = cache.get(id); if (p==null) { p=getFromDataBase(id); cache.store(p); } }
我想要做的是同步ID上的检索,例如
if (p==null) { synchronized (id) { ..retrieve, store } }
不幸的是,这不会工作,因为2个独立的调用可以有相同的Integer id值,但是一个不同的Integer对象,所以他们不会共享锁,并且不会发生同步。
有一个简单的方法来确保你有相同的Integer实例吗? 例如,这个工作:
syncrhonized (Integer.valueOf(id.intValue())){
Integer.valueOf()的javadoc似乎意味着你可能会得到相同的实例,但是这看起来不是一个保证:
返回表示指定的int值的Integer实例。 如果不需要新的Integer实例,则通常应优先使用此方法,而不是构造函数Integer(int),因为通过caching频繁请求的值,此方法可能会产生显着更好的空间和时间性能。
所以,关于如何获得保证相同的Integer实例的任何build议,除了更复杂的解决scheme,如保持Lock对象的WeakHashMap键入int? (没有什么不对的地方,这似乎是必须有一个明显的一行,而不是我失踪)。
你真的不想在Integer
上同步,因为你不能控制哪些实例是相同的,哪些实例是不同的。 Java只是不提供这样的设施(除非你使用小范围内的整数),这在不同的JVM中是可靠的。 如果你真的必须在Integer上进行同步,那么你需要保留一个Map或者Integer集合,这样你就可以保证你得到了你想要的确切的实例。
更好的办法是创build一个新的对象,可能存储在一个由Integer
键入的HashMap
中,以便同步。 像这样的东西:
public Page getPage(Integer id) { Page p = cache.get(id); if (p == null) { synchronized (getCacheSyncObject(id)) { p = getFromDataBase(id); cache.store(p); } } } private ConcurrentMap<Integer, Integer> locks = new ConcurrentHashMap<Integer, Integer>(); private Object getCacheSyncObject(final Integer id) { locks.putIfAbsent(id, id); return locks.get(id); }
为了解释这段代码,它使用了ConcurrentMap
,它允许使用putIfAbsent
。 你可以这样做:
locks.putIfAbsent(id, new Object());
但是这样会导致为每个访问创build对象的(小)成本。 为了避免这种情况,我只是将Integer本身保存在Map
。 这实现了什么? 为什么这与使用Integer本身有什么不同呢?
当你从一个Map
get()
一个get()
,这个关键字将和equals()
进行比较(或者至less所使用的方法是等价于equals()
)。 具有相同值的两个不同的整数实例将彼此相等。 因此,您可以将“ new Integer(5)
”的任意数量的不同Integer实例作为parameter passing给getCacheSyncObject
并且您将始终只返回传入的第一个包含该值的实例。
有些原因可能导致你不想在Integer
上进行同步…如果多个线程正在Integer
对象上进行同步,并且在他们想要使用不同的锁的时候无意中使用相同的锁,那么你可能会陷入死锁。 你可以通过使用
locks.putIfAbsent(id, new Object());
版本,从而导致每次访问caching的(非常)小的成本。 这样做,你可以保证这个类将在一个没有其他类将同步的对象上进行同步。 总是一件好事。
使用线程安全的映射,如ConcurrentHashMap
。 这将允许您安全地操作地图,但使用不同的锁来做真正的计算。 通过这种方式,您可以同时运行多个计算单个地图。
使用ConcurrentMap.putIfAbsent
,而不是放置实际值,而是使用带有计算轻结构的Future
。 可能是FutureTask
实现。 运行计算,然后get
的结果,将线程安全阻塞,直到完成。
Integer.valueOf()
只返回有限范围的caching实例。 你还没有指定你的范围,但总的来说,这是行不通的。
不过,我强烈build议你不要采用这种方法,即使你的值在正确的范围内。 由于这些caching的Integer
实例可用于任何代码,因此无法完全控制可能导致死锁的同步。 这是人们试图lockingString.intern()
的结果的同样的问题。
最好的锁是一个私有variables。 由于只有您的代码可以引用它,所以可以保证不会发生死锁。
顺便说一句,使用WeakHashMap
也不行。 如果作为密钥的实例未被引用,则将被垃圾收集。 如果强烈引用,则可以直接使用。
在整数上使用同步听起来真的是错误的devise。
如果您只需要在检索/存储期间单独同步每个项目,则可以创build一个Set并在那里存储当前locking的项目。 换句话说,
// this contains only those IDs that are currently locked, that is, this // will contain only very few IDs most of the time Set<Integer> activeIds = ... Object retrieve(Integer id) { // acquire "lock" on item #id synchronized(activeIds) { while(activeIds.contains(id)) { try { activeIds.wait(); } catch(InterruptedExcption e){...} } activeIds.add(id); } try { // do the retrieve here... return value; } finally { // release lock on item #id synchronized(activeIds) { activeIds.remove(id); activeIds.notifyAll(); } } }
同样的商店。
底线是:没有一行代码完全按照你需要的方式解决这个问题。
用Integer对象作为键的ConcurrentHashMap怎么样?
你可以看看这个代码来创build一个ID的互斥体。 代码是为String ID编写的,但是可以很容易地为Integer对象编辑。
请参见Java并发实践中的第5.6节:“构build高效,可伸缩的结果caching”。 它涉及您正在尝试解决的确切问题。 特别是,检查memoizer模式。
替代文字http://www.cs.umd.edu/class/fall2008/cmsc433/jcipMed.jpg
正如你从各种答案中可以看到的,有很多方法可以给这只猫蒙皮:
- Goetz等人保留FutureTaskscaching的方法在这种情况下工作的很好,在这种情况下,“无论如何都要caching”,所以不要介意构build一个FutureTask对象的地图(如果你确实介意地图的增长,至less修剪它是很容易的)
- 作为“如何lockingID”的一般答案,Antonio所概括的方法的优点是,当锁的地图被添加到/从中移除时,这是显而易见的。
您可能需要注意Antonio的实现中潜在的问题,即notifyAll()将唤醒等待所有 ID的线程,当其中一个 ID可用时,在高争用情况下可能无法很好地扩展。 原则上,我认为你可以通过为每个当前locking的ID设置一个Condition对象来解决这个问题,这就是你等待/发出的信号。 当然,如果在实践中,在任何时候都很less有一个以上的ID被等待,那么这不是问题。
史蒂夫,
你提出的代码有一堆同步问题。 (安东尼奥也是这样)。
总结:
- 你需要caching一个昂贵的对象。
- 您需要确保一个线程正在执行检索时,另一个线程也不会尝试检索同一个对象。
- 对于n线程,所有试图获取对象的对象都被检索并返回。
- 对于请求不相互竞争的不同对象的线程来说。
伪代码来做到这一点(使用ConcurrentHashMap作为caching):
ConcurrentMap<Integer, java.util.concurrent.Future<Page>> cache = new ConcurrentHashMap<Integer, java.util.concurrent.Future<Page>>; public Page getPage(Integer id) { Future<Page> myFuture = new Future<Page>(); cache.putIfAbsent(id, myFuture); Future<Page> actualFuture = cache.get(id); if ( actualFuture == myFuture ) { // I am the first w00t! Page page = getFromDataBase(id); myFuture.set(page); } return actualFuture.get(); }
注意:
- java.util.concurrent.Future是一个接口
- java.util.concurrent.Future实际上并不具有set(),而是查看实现Future的现有类,以了解如何实现自己的Future(或使用FutureTask)
- 将实际检索推到工作线程几乎肯定是一个好主意。