Java 同步整数值

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/659915/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 17:31:04  来源:igfitidea点击:

Synchronizing on an Integer value

javasynchronization

提问by Steve B.

Possible Duplicate:
What is the best way to increase number of locks in java

可能的重复:
在 Java 中增加锁数量的最佳方法是什么

Suppose I want to lock based on an integer id value. In this case, there's a function that pulls a value from a cache and does a fairly expensive retrieve/store into the cache if the value isn't there.

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

The existing code isn't synchronized and could potentially trigger multiple retrieve/store operations:

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

//psuedocode
public Page getPage (Integer id){
   Page p = cache.get(id);
   if (p==null)
   {
      p=getFromDataBase(id);
      cache.store(p);
   }
}

What I'd like to do is synchronize the retrieve on the id, e.g.

我想做的是同步对 id 的检索,例如

   if (p==null)
   {
       synchronized (id)
       {
        ..retrieve, store
       }
   }

Unfortunately this won't work because 2 separate calls can have the same Integer id value but a different Integer object, so they won't share the lock, and no synchronization will happen.

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

Is there a simple way of insuring that you have the same Integer instance? For example, will this work:

有没有一种简单的方法可以确保您拥有相同的 Integer 实例?例如,这是否有效:

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

The javadoc for Integer.valueOf() seems to imply that you're likely to get the same instance, but that doesn't look like a guarantee:

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

Returns a Integer instance representing the specified int value. If a new Integer instance is not required, this method should generally be used in preference to the constructor Integer(int), as this method is likely to yield significantly better space and time performance by caching frequently requested values.

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

So, any suggestions on how to get an Integer instance that's guaranteed to be the same, other than the more elaborate solutions like keeping a WeakHashMap of Lock objects keyed to the int? (nothing wrong with that, it just seems like there must be an obvious one-liner than I'm missing).

那么,除了更复杂的解决方案(例如将 Lock 对象的 WeakHashMap 保持为 int 键)之外,关于如何获得保证相同的 Integer 实例的任何建议?(这没有错,似乎必须有一个明显的单行比我缺少的)。

采纳答案by Eddie

You really don't want to synchronize on an Integer, since you don't have control over what instances are the same and what instances are different. Java just doesn't provide such a facility (unless you're using Integers in a small range) that is dependable across different JVMs. If you really must synchronize on an Integer, then you need to keep a Map or Set of Integer so you can guarantee that you're getting the exact instance you want.

您真的不想在 上同步Integer,因为您无法控制哪些实例是相同的,哪些实例是不同的。Java 只是不提供这种在不同 JVM 中可靠的工具(除非您在小范围内使用整数)。如果你真的必须在一个整数上同步,那么你需要保留一个 Map 或 Set of Integer 以便你可以保证你得到你想要的确切实例。

Better would be to create a new object, perhaps stored in a HashMapthat is keyed by the Integer, to synchronize on. Something like this:

更好的是创建一个新对象,可能存储在HashMap由 键控的a中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);
}

To explain this code, it uses ConcurrentMap, which allows use of putIfAbsent. You could do this:

为了解释这段代码,它使用ConcurrentMap,它允许使用putIfAbsent. 你可以这样做:

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

but then you incur the (small) cost of creating an Object for each access. To avoid that, I just save the Integer itself in the Map. What does this achieve? Why is this any different from just using the Integer itself?

但是随后您会产生为每次访问创建对象的(小)成本。为了避免这种情况,我只是将 Integer 本身保存在Map. 这达到了什么目的?为什么这与仅使用 Integer 本身有什么不同?

When you do a get()from a Map, the keys are compared with equals()(or at least the method used is the equivalent of using equals()). Two different Integer instances of the same value will be equal to each other. Thus, you can pass any number of different Integer instances of "new Integer(5)" as the parameter to getCacheSyncObjectand you will always get back only the very first instance that was passed in that contained that value.

当您get()从 a执行 a时Map,键将与equals()(或至少使用的方法等效于 using equals())。相同值的两个不同 Integer 实例将彼此相等。因此,您可以将任意数量的 " new Integer(5)"的不同 Integer 实例作为参数传递给,getCacheSyncObject并且您将始终只返回包含该值的第一个实例。

There are reasons why you may not want to synchronize on Integer... you can get into deadlocks if multiple threads are synchronizing on Integerobjects and are thus unwittingly using the same locks when they want to use different locks. You can fix this risk by using the

您可能不希望同步的原因有很多Integer……如果多个线程在Integer对象上同步,并且在他们想要使用不同的锁时无意中使用了相同的锁,您可能会陷入死锁。您可以通过使用

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

version and thus incurring a (very) small cost to each access to the cache. Doing this, you guarantee that this class will be doing its synchronization on an object that no other class will be synchronizing on. Always a Good Thing.

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

回答by erickson

Integer.valueOf()only returns cached instances for a limited range. You haven't specified your range, but in general, this won't work.

Integer.valueOf()只返回有限范围内的缓存实例。您还没有指定您的范围,但一般来说,这是行不通的。

However, I would strongly recommend you not take this approach, even if your values are in the correct range. Since these cached Integerinstances are available to any code, you can't fully control the synchronization, which could lead to a deadlock. This is the same problem people have trying to lock on the result of String.intern().

但是,我强烈建议您不要采用这种方法,即使您的值在正确的范围内。由于这些缓存Integer实例可供任何代码使用,因此您无法完全控制同步,这可能会导致死锁。这与人们试图锁定String.intern().

The best lock is a private variable. Since only your code can reference it, you can guarantee that no deadlocks will occur.

最好的锁是私有变量。由于只有您的代码可以引用它,因此您可以保证不会发生死锁。

By the way, using a WeakHashMapwon't work either. If the instance serving as the key is unreferenced, it will be garbage collected. And if it is strongly referenced, you could use it directly.

顺便说一句,使用 aWeakHashMap也行不通。如果作为键的实例未被引用,它将被垃圾收集。如果它被强引用,你可以直接使用它。

回答by starblue

How about a ConcurrentHashMap with the Integer objects as keys?

以 Integer 对象为键的 ConcurrentHashMap 怎么样?

回答by McDowell

You could have a look at this codefor creating a mutex from an ID. The code was written for String IDs, but could easily be edited for Integer objects.

您可以查看此代码以从 ID 创建互斥锁。代码是为字符串 ID 编写的,但可以很容易地为整数对象进行编辑。

回答by Antonio

Using synchronized on an Integer sounds really wrong by design.

在 Integer 上使用 synchronized 听起来确实是错误的设计。

If you need to synchronize each item individually only during retrieve/store you can create a Set and store there the currently locked items. In another words,

如果您只需要在检索/存储期间单独同步每个项目,您可以创建一个 Set 并将当前锁定的项目存储在那里。换句话说,

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

The same goes to the store.

去商店也是一样。

The bottom line is: there is no single line of code that solves this problem exactly the way you need.

底线是:没有一行代码可以完全按照您的需要解决这个问题。

回答by Tom Hawtin - tackline

Use a thread-safe map, such as ConcurrentHashMap. This will allow you to manipulate a map safely, but use a different lock to do the real computation. In this way you can have multiple computations running simultaneous with a single map.

使用线程安全映射,例如ConcurrentHashMap. 这将允许您安全地操作地图,但使用不同的锁来进行真正的计算。通过这种方式,您可以在单个地图上同时运行多个计算。

Use ConcurrentMap.putIfAbsent, but instead of placing the actual value, use a Futurewith computationally-light construction instead. Possibly the FutureTaskimplementation. Run the computation and then getthe result, which will thread-safely block until done.

使用ConcurrentMap.putIfAbsent,而不是放置实际值,而是使用Future具有计算量轻结构的 a 。可能是FutureTask执行。运行计算,然后运行get结果,这将线程安全地阻塞直到完成。

回答by Julien Chastang

See section 5.6 in Java Concurrency in Practice: "Building an efficient, scalable, result cache". It deals with the exact issue you are trying to solve. In particular, check out the memoizer pattern.

请参阅Java 并发实践中的第 5.6 节:“构建高效、可扩展的结果缓存”。它处理您试图解决的确切问题。特别是,查看 memoizer 模式。

alt text
(source: umd.edu)

替代文字
(来源:umd.edu

回答by Neil Coffey

As you can see from the variety of answers, there are various ways to skin this cat:

从各种回答中可以看出,给这只猫剥皮的方法有很多种:

  • Goetz et al's approach of keeping a cache of FutureTasks works quite well in situations like this where you're "caching something anyway" so don't mind building up a map of FutureTask objects (and if you did mind the map growing, at least it's easy to make pruning it concurrent)
  • As a general answer to "how to lock on ID", the approach outlined by Antonio has the advantage that it's obvious when the map of locks is added to/removed from.
  • Goetz 等人保留 FutureTasks 缓存的方法在这样的情况下非常有效,在这种情况下,您“无论如何都在缓存某些东西”,因此不要介意构建 FutureTask 对象的映射(如果您确实介意映射增长,至少很容易使修剪它并发)
  • 作为“如何锁定 ID”的一般答案,Antonio 概述的方法的优点是在添加/删除锁映射时很明显。

You may need to watch out for a potential issue with Antonio's implementation, namely that the notifyAll() will wake up threads waiting on allIDs when oneof them becomes available, which may not scale very well under high contention. In principle, I think you can fix that by having a Condition object for each currently locked ID, which is then the thing that you await/signal. Of course, if in practice there's rarely more than one ID being waited on at any given time, then this isn't an issue.

您可能需要注意 Antonio 的实现的一个潜在问题,即 notifyAll() 将在所有ID之一可用时唤醒等待所有ID 的线程,这在高争用下可能无法很好地扩展。原则上,我认为您可以通过为每个当前锁定的 ID 设置一个 Condition 对象来解决这个问题,这就是您等待/发出信号的东西。当然,如果实际上在任何给定时间等待的 ID 很少超过一个,那么这不是问题。

回答by Pat

Steve,

史蒂夫,

your proposed code has a bunch of problems with synchronization. (Antonio's does as well).

您提出的代码在同步方面存在很多问题。(安东尼奥的也是如此)。

To summarize:

总结一下:

  1. You need to cache an expensive object.
  2. You need to make sure that while one thread is doing the retrieval, another thread does not also attempt to retrieve the same object.
  3. That for n-threads all attempting to get the object only 1 object is ever retrieved and returned.
  4. That for threads requesting different objects that they do not contend with each other.
  1. 您需要缓存一个昂贵的对象。
  2. 您需要确保当一个线程正在执行检索时,另一个线程也不会尝试检索相同的对象。
  3. 对于所有尝试获取对象的 n 线程来说,只有 1 个对象被检索和返回。
  4. 对于请求不同对象的线程,它们不会相互竞争。

pseudo code to make this happen (using a ConcurrentHashMap as the cache):

实现这一点的伪代码(使用 ConcurrentHashMap 作为缓存):

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

Note:

笔记:

  1. java.util.concurrent.Future is an interface
  2. java.util.concurrent.Future does not actually have a set() but look at the existing classes that implement Future to understand how to implement your own Future (Or use FutureTask)
  3. Pushing the actual retrieval to a worker thread will almost certainly be a good idea.
  1. java.util.concurrent.Future 是一个接口
  2. java.util.concurrent.Future 实际上并没有 set() 而是查看实现 Future 的现有类以了解如何实现您自己的 Future (或使用 FutureTask)
  3. 将实际检索推送到工作线程几乎肯定是一个好主意。