由于 statckoverflow 不允许在原始问题中添加更多内容(您只能添加注释,不能添加代码),我在这里向我的原始问题提出一个连续的问题: Can we use Synchronized for each entry instead of ConcurrentHashMap?
问题很简单,我不知道为什么对于一个可能很多人都遇到过的简单问题我要花这么多时间:/
问题是:我有一个 HashMap ,我希望当一个线程正在处理 HashMap 的条目之一时,没有任何其他线程访问该对象,并且我不想锁定整个 HashMap 。
我知道java提供了ConcurrentHashMap,但是当你想做比简单的put和get更复杂的事情时,ConcurrentHashMap并不能解决问题。即使是像合并这样新添加的功能(在 Java 8 中)也不足以应对复杂的场景。
例如:
假设我想要一个将字符串映射到 ArrayList 的 HashMap 。那么例如假设我想这样做: 对于键 k,如果有任何条目,则将 newString 添加到其 ArrayList,但如果 k 没有条目,则为 k 创建条目,使其 ArrayList 具有 newString。
我想我可以这样做:
ArrayList<String> tm =new ArrayList<String>();
tm.add(newString);
Object result = map.putIfAbsent(k, tm);
if (result != null)
{
map.get(k).add(newString);
}
但是不起作用,为什么?假设 putIfAbset 返回除 null 之外的其他内容,则意味着映射已经具有键为 k 的条目,因此我将尝试将 newString 添加到已存在条目的 ArrayList 中,但在添加之前,另一个线程可能会删除该条目,并且然后我会得到 NullPointerException!
所以,我发现正确编码这些东西非常困难。
但我在想,如果我能简单地锁定那个条目,生活就会很精彩!
在我的上一篇文章中,我提出了一些非常简单的建议,实际上消除了对并发HashMap的需要,并提供入门级锁定,但有人说这不是真的,因为 Long 不是不可变的......我没有很好地理解它。
现在,我实现并测试了它,它对我来说看起来不错,但我不知道为什么其他更有经验的开发人员告诉我它不是线程安全的:(
这是我测试过的确切代码:
主线程:
import java.util.HashMap;
public class mainThread {
public static HashMap<String, Long> map = new HashMap<String, Long>();
public static void main (String args[])
{
map.put("k1", new Long(32));
synchronized(map.get("k1"))
{
Thread t = new Thread(new threadA());
t.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
线程A:
public class ThreadA implements Runnable {
@Override
public void run() {
mainThread.map.put("k2", new Long(21));
System.out.println(mainThread.map.get("k2"));
synchronized (mainThread.map.get("k1")) {
System.out.println("Insdie synchronized of threadA");
}
}
}
效果很好!打印21,5秒后,mainThread释放map.get("k1")的锁,打印“Insdie Synchronized of threadA”
那么,为什么使用这种简单的方法我们不能提供入门级锁定?!为什么并发应该那么复杂哈哈(开玩笑)
最佳答案
首先,据我所知,没有提供入门级锁定的标准 map 实现。
但我认为你可以避免这样做。例如
更新...更正错误
ArrayList<String> tm = new ArrayList<String>();
ArrayList<String> old = map.putIfAbsent(k, tm);
if (old != null) {
tm = old;
}
synchronized (tm) {
// can now add / remove entries and this will appear as an atomic
// actions to other threads that are using `synchronized` to
// access or update the list
tm.add(string1);
tm.add(string2);
}
是的,另一个线程可能会在该线程(可能)插入它和该线程锁定它之间更新 HashMap 条目中的列表。不过,这并不重要。 (已更正的)putIfAbsent
和随后的测试可确保每个人都将使用并锁定相同的列表。
(假设:所有线程在插入/更新条目时都使用此逻辑。)
如果列表变空,则原子删除列表很困难,但我认为通常没有必要这样做。
更新2
有更好的方法:
ArrayList<String> tm = map.computeIfAbsent(k, ArrayList::new);
synchronized (tm) {
...
}
(感谢斯图尔特)
更新3
We can do it with merger too.
也许,是的。像这样的事情:
ArrayList<String> tm = new ArrayList<String>;
tm.add(...);
...
map.merge(key, tm, (oldV, newV) -> {oldV.addAll(newV); return oldV});
缺点是您要双重处理 tm
的所有元素;即添加到 2 个单独的列表(其中一个被你扔掉)。
但你也可以这样做:
map.merge(key, tm, (oldV, newV) -> {
oldV.removeAll(newV);
return oldV.size() == 0 ? null : oldV}
);
让我担心的是 javadoc没有明确声明在发生这种情况时值 oldV
将被锁定。它说:
"The entire method invocation is performed atomically. Some attempted update operations on this map by other threads may be blocked while computation is in progress ..."
...但它没有明确指出发生这种情况时值存在互斥。 (例如,将此方法与 putIfAbsent
/computeIfAbsent
和显式 synchronized
block 混合使用很可能是危险的。锁定很可能处于开启状态不同的对象。)
关于java - 如何在HashMap中实现入门级锁定?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38494042/