Java 同步块与 Collections.synchronizedMap

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/567068/
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 16:19:08  来源:igfitidea点击:

Java synchronized block vs. Collections.synchronizedMap

javasynchronization

提问by Ryan Ahearn

Is the following code set up to correctly synchronize the calls on synchronizedMap?

以下代码是否设置为正确同步调用synchronizedMap

public class MyClass {
  private static Map<String, List<String>> synchronizedMap = Collections.synchronizedMap(new HashMap<String, List<String>>());

  public void doWork(String key) {
    List<String> values = null;
    while ((values = synchronizedMap.remove(key)) != null) {
      //do something with values
    }
  }

  public static void addToMap(String key, String value) {
    synchronized (synchronizedMap) {
      if (synchronizedMap.containsKey(key)) {
        synchronizedMap.get(key).add(value);
      }
      else {
        List<String> valuesList = new ArrayList<String>();
        valuesList.add(value);
        synchronizedMap.put(key, valuesList);
      }
    }
  }
}

From my understanding, I need the synchronized block in addToMap()to prevent another thread from calling remove()or containsKey()before I get through the call to put()but I do not need a synchronized block in doWork()because another thread cannot enter the synchronized block in addToMap()before remove()returns because I created the Map originally with Collections.synchronizedMap(). Is that correct? Is there a better way to do this?

根据我的理解,我需要同步块addToMap()以防止另一个线程调用remove()containsKey()在我通过调用之前,put()但我不需要同步块,doWork()因为另一个线程无法addToMap()remove()返回之前进入同步块,因为我最初创建了 Map与Collections.synchronizedMap(). 那是对的吗?有一个更好的方法吗?

采纳答案by Yuval Adam

Collections.synchronizedMap()guarantees that each atomic operation you want to run on the map will be synchronized.

Collections.synchronizedMap()保证您要在地图上运行的每个原子操作都将同步。

Running two (or more) operations on the map however, must be synchronized in a block. So yes - you are synchronizing correctly.

然而,在地图上运行两个(或更多)操作必须在一个块中同步。所以是的 - 您正在正确同步。

回答by TofuBeer

If you are using JDK 6 then you might want to check out ConcurrentHashMap

如果您使用的是 JDK 6,那么您可能需要查看ConcurrentHashMap

Note the putIfAbsent method in that class.

请注意该类中的 putIfAbsent 方法。

回答by Paul Tomblin

That looks correct to me. If I were to change anything, I would stop using the Collections.synchronizedMap() and synchronize everything the same way, just to make it clearer.

这对我来说看起来是正确的。如果我要更改任何内容,我将停止使用 Collections.synchronizedMap() 并以相同的方式同步所有内容,只是为了使其更清晰。

Also, I'd replace

另外,我会替换

  if (synchronizedMap.containsKey(key)) {
    synchronizedMap.get(key).add(value);
  }
  else {
    List<String> valuesList = new ArrayList<String>();
    valuesList.add(value);
    synchronizedMap.put(key, valuesList);
  }

with

List<String> valuesList = synchronziedMap.get(key);
if (valuesList == null)
{
  valuesList = new ArrayList<String>();
  synchronziedMap.put(key, valuesList);
}
valuesList.add(value);

回答by Barend

Check out Google Collections' Multimap, e.g. page 28 of this presentation.

查看Google Collections' Multimap,例如本演示文稿的第 28 页。

If you can't use that library for some reason, consider using ConcurrentHashMapinstead of SynchronizedHashMap; it has a nifty putIfAbsent(K,V)method with which you can atomically add the element list if it's not already there. Also, consider using CopyOnWriteArrayListfor the map values if your usage patterns warrant doing so.

如果由于某种原因不能使用该库,请考虑使用ConcurrentHashMap代替SynchronizedHashMap; 它有一个漂亮的putIfAbsent(K,V)方法,如果元素列表还没有,你可以用它原子地添加元素列表。此外,CopyOnWriteArrayList如果您的使用模式允许这样做,请考虑使用地图值。

回答by JLR

There is the potentialfor a subtle bug in your code.

还有就是潜在的代码中的一个微妙的错误。

[UPDATE:Since he's using map.remove() this description isn't totally valid. I missed that fact the first time thru. :( Thanks to the question's author for pointing that out. I'm leaving the rest as is, but changed the lead statement to say there is potentiallya bug.]

[更新:由于他使用的是 map.remove(),所以这个描述并不完全有效。我第一次错过了这个事实。:(感谢问题的作者指出这一点。我将其余部分保持原样,但更改了主要声明以表示可能存在错误。]

In doWork()you get the List value from the Map in a thread-safe way. Afterward, however, you are accessing that list in an unsafe matter. For instance, one thread may be using the list in doWork()while another thread invokes synchronizedMap.get(key).add(value)in addToMap(). Those two access are not synchronized. The rule of thumb is that a collection's thread-safe guarantees don't extend to the keys or values they store.

doWork() 中,您以线程安全的方式从 Map 获取 List 值。但是,之后您正在以不安全的方式访问该列表。例如,一个线程可能在doWork() 中使用列表,而另一个线程在addToMap() 中调用synchronizedMap.get(key).add(value )。这两个访问不同步。经验法则是集合的线程安全保证不会扩展到它们存储的键或值。

You could fix this by inserting a synchronized list into the map like

您可以通过在地图中插入一个同步列表来解决这个问题

List<String> valuesList = new ArrayList<String>();
valuesList.add(value);
synchronizedMap.put(key, Collections.synchronizedList(valuesList)); // sync'd list

Alternatively you could synchronize on the map while you access the list in doWork():

或者,您可以在访问doWork() 中的列表时在地图上同步:

  public void doWork(String key) {
    List<String> values = null;
    while ((values = synchronizedMap.remove(key)) != null) {
      synchronized (synchronizedMap) {
          //do something with values
      }
    }
  }

The last option will limit concurrency a bit, but is somewhat clearer IMO.

最后一个选项会稍微限制并发性,但 IMO 更清晰一些。

Also, a quick note about ConcurrentHashMap. This is a really useful class, but is not always an appropriate replacement for synchronized HashMaps. Quoting from its Javadocs,

另外,关于 ConcurrentHashMap 的快速说明。这是一个非常有用的类,但并不总是同步 HashMap 的合适替代品。引用其 Javadocs,

This class is fully interoperable with Hashtable in programs that rely on its thread safety but not on its synchronization details.

在依赖其线程安全但不依赖于其同步细节的程序中,此类与 Hashtable 完全可互操作。

In other words, putIfAbsent() is great for atomic inserts but does not guarantee other parts of the map won't change during that call; it guarantees only atomicity. In your sample program, you are relying on the synchronization details of (a synchronized) HashMap for things other than put()s.

换句话说, putIfAbsent() 非常适合原子插入,但不能保证映射的其他部分在该调用期间不会更改;它只保证原子性。在您的示例程序中,除了 put() 之外,您依赖(同步的)HashMap 的同步细节。

Last thing. :) This great quote from Java Concurrency in Practicealways helps me in designing an debugging multi-threaded programs.

最后一件事。:) 这个来自Java Concurrency in Practice 的精彩引述总是帮助我设计调试多线程程序。

For each mutable state variable that may be accessed by more than one thread, all accesses to that variable must be performed with the same lock held.

对于可以被多个线程访问的每个可变状态变量,对该变量的所有访问都必须在持有相同锁的情况下执行。

回答by Sergey

Yes, you are synchronizing correctly. I will explain this in more detail. You must synchronize two or more method calls on the synchronizedMap object only in a case you have to rely on results of previous method call(s) in the subsequent method call in the sequence of method calls on the synchronizedMap object. Let's take a look at this code:

是的,您正在正确同步。我将更详细地解释这一点。您必须同步对 synchronizedMap 对象的两个或多个方法调用,只有在您必须依赖对 synchronizedMap 对象的方法调用序列中的后续方法调用中先前方法调用的结果的情况下。我们来看看这段代码:

synchronized (synchronizedMap) {
    if (synchronizedMap.containsKey(key)) {
        synchronizedMap.get(key).add(value);
    }
    else {
        List<String> valuesList = new ArrayList<String>();
        valuesList.add(value);
        synchronizedMap.put(key, valuesList);
    }
}

In this code

在这段代码中

synchronizedMap.get(key).add(value);

and

synchronizedMap.put(key, valuesList);

method calls are relied on the result of the previous

方法调用依赖于前面的结果

synchronizedMap.containsKey(key)

method call.

方法调用。

If the sequence of method calls were not synchronized the result might be wrong. For example thread 1is executing the method addToMap()and thread 2is executing the method doWork()The sequence of method calls on the synchronizedMapobject might be as follows: Thread 1has executed the method

如果方法调用的顺序不同步,结果可能是错误的。例如thread 1正在执行方法addToMap()thread 2正在执行方法对象doWork()上的方法调用顺序synchronizedMap可能如下: Thread 1已执行方法

synchronizedMap.containsKey(key)

and the result is "true". After that operating system has switched execution control to thread 2and it has executed

结果是“ true”。在该操作系统将执行控制切换到thread 2并执行之后

synchronizedMap.remove(key)

After that execution control has been switched back to the thread 1and it has executed for example

之后,执行控制已切换回thread 1并且它已执行例如

synchronizedMap.get(key).add(value);

believing the synchronizedMapobject contains the keyand NullPointerExceptionwill be thrown because synchronizedMap.get(key)will return null. If the sequence of method calls on the synchronizedMapobject is not dependent on the results of each other then you don't need to synchronize the sequence. For example you don't need to synchronize this sequence:

相信synchronizedMap对象包含key并且NullPointerException会被抛出,因为synchronizedMap.get(key)会返回null。如果synchronizedMap对象上方法调用的顺序不依赖于彼此的结果,则不需要同步该顺序。例如,您不需要同步此序列:

synchronizedMap.put(key1, valuesList1);
synchronizedMap.put(key2, valuesList2);

Here

这里

synchronizedMap.put(key2, valuesList2);

method call does not rely on the results of the previous

方法调用不依赖于前面的结果

synchronizedMap.put(key1, valuesList1);

method call (it does not care if some thread has interfered in between the two method calls and for example has removed the key1).

方法调用(它不关心是否某个线程干扰了两个方法调用,例如删除了key1)。

回答by Jai Pandit

The way you have synchronized is correct. But there is a catch

您同步的方式是正确的。但是有一个问题

  1. Synchronized wrapper provided by Collection framework ensures that the method calls I.e add/get/contains will run mutually exclusive.
  1. Collection 框架提供的同步包装器确保方法调用即 add/get/contains 将运行互斥。

However in real world you would generally query the map before putting in the value. Hence you would need to do two operations and hence a synchronized block is needed. So the way you have used it is correct. However.

但是在现实世界中,您通常会在输入值之前查询地图。因此你需要做两个操作,因此需要一个同步块。所以你使用它的方式是正确的。然而。

  1. You could have used a concurrent implementation of Map available in Collection framework. 'ConcurrentHashMap' benefit is
  1. 您可以使用 Collection 框架中可用的 Map 的并发实现。'ConcurrentHashMap' 的好处是

a. It has a API 'putIfAbsent' which would do the same stuff but in a more efficient manner.

一种。它有一个 API 'putIfAbsent' 可以做同样的事情,但以更有效的方式。

b. Its Efficient: dThe CocurrentMap just locks keys hence its not blocking the whole map's world. Where as you have blocked keys as well as values.

湾 它的效率:dThe CocurrentMap 只是锁定键,因此它不会阻塞整个地图的世界。因为您已经阻止了键和值。

c. You could have passed the reference of your map object somewhere else in your codebase where you/other dev in your tean may end up using it incorrectly. I.e he may just all add() or get() without locking on the map's object. Hence his call won't run mutually exclusive to your sync block. But using a concurrent implementation gives you a peace of mind that it can never be used/implemented incorrectly.

C。您可能已经在代码库中的其他地方传递了地图对象的引用,您/团队中的其他开发人员最终可能会错误地使用它。即他可能只是所有的 add() 或 get() 而不锁定地图的对象。因此,他的调用不会与您的同步块互斥。但是使用并发实现可以让您高枕无忧,它永远不会被错误地使用/实现。