java - 如何在HashMap中实现入门级锁定?

标签 java concurrency hashmap

由于 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/

相关文章:

java - 错误 : A JNI error has occurred, 请检查您的安装并重试。NoClassDefFounderror: DRPC ExecutionException

java - 使用 JAXB 映射包含父类(super class)型和子类型的 Java 集合

java - 使用 docker stack 部署时 Hazelcast 节点未发现其他节点

java - 结合 putIfAbsent 并替换为 ConcurrentMap

scala - 服务于一项任务的多个 Scala 参与者

python - 对于将 Python Lock 对象存储在 Beaker session 中,我应该有什么顾虑吗?

java - 为什么子列表不适用于 List<Object>?

Java : improve the smoothness of a slow thread animation?

java - HashMap 做 containsKey 的方式不符合预期

java - 垂直打印 HashMap