一尘不染

Java同步整数值

java

假设我想基于整数id值进行锁定。在这种情况下,有一个函数可从缓存中提取一个值,如果该值不存在,则会进行相当昂贵的检索/存储到缓存中。

现有代码不同步,并且可能触发多个检索/存储操作:

//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
       }
   }

不幸的是,这是行不通的,因为两个单独的调用可以具有相同的Integer id值,但是可以具有不同的Integer对象,因此它们将不会共享锁,并且不会发生同步。

有没有一种简单的方法来确保您拥有相同的Integer实例?例如,这将工作:

 syncrhonized (Integer.valueOf(id.intValue())){

Integer.valueOf()的javadoc似乎暗示您很可能会获得相同的实例,但这看起来不像是保证:

返回表示指定int值的Integer实例。如果不需要新的Integer实例,则通常应优先于构造方法Integer(int)使用此方法,因为此方法通过缓存经常请求的值可能会产生明显更好的空间和时间性能。

因此,除了更复杂的解决方案(例如将Lock对象的WeakHashMap保留为int键)以外,还有什么建议可以保证保证Integer实例相同?(这没有错,似乎必须有一个明显的单线而不是我所缺少的)。


阅读 349

收藏
2020-03-24

共1个答案

一尘不染

你真的不想在上同步Integer,因为你无法控制哪些实例相同,哪些实例不同。Java只是没有提供跨不同JVM可靠的功能(除非你在较小范围内使用Integers)。如果确实必须在Integer上进行同步,则需要保留Map或Integer Set,以便可以保证获得所需的确切实例。

最好是创建一个新对象,也许将其存储在以HashMap键为键的对象中以Integer进行同步。像这样:

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());

但随后你需要为每次访问创建一个对象,而这笔费用很小。为避免这种情况,我只将Integer本身保存在中Map。这能达到什么目的?为什么这与仅使用Integer本身有什么不同?

当你get()从中执行a时Map,会将键与进行比较equals()(或至少使用的方法等效于使用equals())。具有相同值的两个不同的Integer实例将彼此相等。因此,你可以将“ new Integer(5)”的任意数量的不同Integer实例作为参数传递给getCacheSyncObject你,并且你将始终始终只获取在包含该值的那个实例中传递的第一个实例。

有一些原因可能导致你不希望在Integer... 上进行同步…如果多个线程在Integer对象上进行同步,从而在它们想要使用不同的锁时不经意地使用相同的锁,则可能会陷入死锁。你可以使用

  locks.putIfAbsent(id, new Object());

版本,因此每次访问缓存都会产生(非常小的)成本。这样做,你可以确保此类将在没有其他类进行同步的对象上进行同步。永远是一件好事。

2020-03-24