在整数值上同步

可能重复:
在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被等待,那么这不是问题。

史蒂夫,

你提出的代码有一堆同步问题。 (安东尼奥也是这样)。

总结:

  1. 你需要caching一个昂贵的对象。
  2. 您需要确保一个线程正在执行检索时,另一个线程也不会尝试检索同一个对象。
  3. 对于n线程,所有试图获取对象的对象都被检索并返回。
  4. 对于请求不相互竞争的不同对象的线程来说。

伪代码来做到这一点(使用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(); } 

注意:

  1. java.util.concurrent.Future是一个接口
  2. java.util.concurrent.Future实际上并不具有set(),而是查看实现Future的现有类,以了解如何实现自己的Future(或使用FutureTask)
  3. 将实际检索推到工作线程几乎肯定是一个好主意。