首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >我们如何在HashMap中进行入门级锁定?

我们如何在HashMap中进行入门级锁定?
EN

Stack Overflow用户
提问于 2016-07-21 02:46:32
回答 3查看 2.3K关注 0票数 2

由于statckoverflow不允许在原始问题中向问题添加更多内容(您只能添加注释,而不能添加代码),所以我在这里问的问题是:我们可以对每个条目使用同步而不是ConcurrentHashMap吗?

这个问题很简单,我不知道为什么这么简单的问题,也许很多人在我之前遇到过,我应该花这么多时间:/

问题是:我有一个hashmap,我希望当一个线程处理hashMap的一个条目时,没有任何其他线程访问该对象,并且我不想锁定整个hashMap。

我知道java提供了ConcurrentHashMap,但是当您想做比简单的put和get更复杂的事情时,ConcurrentHashMap并不能解决这个问题。即使是新添加的函数(在Java 8中),比如合并,对于复杂的场景也是不够的。

例如:

假设我想要一个将字符串映射到ArrayLists的散列映射。然后,假设我想这样做:对于k键,如果有任何条目,则将newString添加到其ArrayList中,但是如果没有k的条目,则为k创建条目,使其ArrayList具有newString.

我在想,我可以这样做:

代码语言:javascript
复制
                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!

因此,我发现很难正确地编码这些东西。

但我在想,如果我能简单地锁上这个入口,生活将是美好的!

在我的上一篇文章中,我提出了一些非常简单的东西,实际上消除了对concurrentHashMap的需求,并且提供了入门级的锁定,但是有些人说这不是真的,因为Long不是不变的.我没能很好的理解。

现在,我实现并测试了它,它在我看来很好,但是我不知道为什么这里的其他更有经验的开发人员告诉我它不是线程安全的:

这正是我测试过的代码:

MainThread:

代码语言:javascript
复制
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();
        }

    }
}
}

ThreadA:

代码语言:javascript
复制
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 k1 of threadA“

那么,为什么我们不能使用这种简单的方法来提供入门级锁定呢?!为什么并发应该如此复杂,Lol (开玩笑)

EN

回答 3

Stack Overflow用户

发布于 2016-07-21 02:59:21

首先,没有我所知道的标准地图实现,它提供入门级锁定。

但我认为你可以避免这样的需要。例如

更新..。修正错误

代码语言:javascript
复制
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

有一个更好的方法:

代码语言:javascript
复制
ArrayList<String> tm = map.computeIfAbsent(k, ArrayList::new);
synchronized (tm) {
    ...
}

(谢谢斯图尔特)

更新3

我们也可以通过合并做到这一点。

也许,是的。就像这样:

代码语言:javascript
复制
ArrayList<String> tm = new ArrayList<String>;
tm.add(...);
...
map.merge(key, tm, (oldV, newV) -> {oldV.addAll(newV); return oldV});

缺点是,您正在双重处理tm的所有元素;即添加到两个单独的列表中(其中一个是抛出的)。

但你也可以这样做:

代码语言:javascript
复制
map.merge(key, tm, (oldV, newV) -> {
      oldV.removeAll(newV); 
      return oldV.size() == 0 ? null : oldV}
);

我担心的是,javadoc没有显式地声明在发生这种情况时值oldV将被锁定。上面写着:

“整个方法调用都是原子性的。在计算过程中,其他线程在此地图上的一些尝试更新操作可能会被阻止.”

..。但它并没有明确指出,在这种情况发生时,在价值上存在着相互排斥。(例如,将此方法与putIfAbsent / computeIfAbsent以及显式synchronized块混合使用将非常危险。锁很可能在不同的对象上。)

票数 5
EN

Stack Overflow用户

发布于 2016-07-21 03:02:06

嗯,第一个大问题是,您甚至不尝试对put调用执行任何锁定。对于普通的HashMap,这些不是自动的线程安全。您似乎认为单独的HashMap条目是完全独立的,但是HashMaps并不是这样工作的。

即使您修复了put问题(无论如何可能需要ConcurrentHashMap或整体映射锁),实际锁定的部分并不是安全锁定的。

假设线程1put是条目"k1": 1,线程2尝试get("k1")。线程2会看到什么?

get调用完成之前,线程2甚至不会尝试获取任何锁。get调用完全不受保护!没有发生任何情况--在putget之间的关系之前,get调用可能看不到条目,或者看到条目,或者看到映射处于不一致的中间状态,然后可怕地崩溃。

同步get调用的结果太晚了。

票数 0
EN

Stack Overflow用户

发布于 2016-07-21 08:30:32

我想我终于找到了使用合并函数的解决方案。我举一个例子,我会编辑这篇文章,使别人更容易阅读,但我现在只是张贴,以了解您的反馈。

下面是一个ConcurrentHashMap的示例,它的值为ConcurrentHashMaps (就示例而言,23和1只是两个随机值):

代码语言:javascript
复制
    Long intialValue = new Long(3);
    Long addedValue = new Long(10);
    Long removingValue = new Long (5);

    ConcurrentHashMap<Integer, ConcurrentHashMap<Integer, Long>> map = new ConcurrentHashMap<>();

    //Initialization....
    ConcurrentHashMap<Integer, Long> i = new ConcurrentHashMap<Integer, Long>();
    i.put(1, intialValue);
    map.put(23, i);
    //......

    //addition
    ConcurrentHashMap<Integer, Long> c = new ConcurrentHashMap<Integer, Long>();
    c.put(1, addedValue);
    map.merge(23, c, (oldHashMap, newHashMap) -> {
        oldHashMap.merge (1, c.get(1), (oldV, newV) -> { 
            if (oldV < newV) return newV; else return oldV; 
            });
        return oldHashMap;
        });



    //removal
    // we want to remove entry 1 from the inner HashMap if its value is less than 2, and if the entry is empty remove the entry from the outer HashMap 
    ConcurrentHashMap<Integer, Long> r = new ConcurrentHashMap<Integer, Long>();
    r.put(1, removingValue);
    map.merge (23, r, (oldHashMap, newHashMap) -> {
    oldHashMap.merge(1, newHashMap.get(1), (oldV, newV) -> {if  (oldV  < newV) return newV; else return oldV;});
    return oldHashMap;
    });
    map.remove(23, r);

    if (map.containsKey(23))
    {
        System.out.println("Map contains key 23");
        if (map.get(23).containsKey(1))
        {
            System.out.println("The value for <23,1> is " + map.get(23).get(1));
        }
    }

这就是代码的作用:

  1. initialization:首先为键23创建映射并将另一个映射放入其中,其中键1的值为initialValue
  2. 加法:然后检查,1)如果键23没有值,它会放置一个值为键1的值addedValue的映射,否则2)如果键23已经有一个值,它会检查它的值如果值小于addedValue,它会用addedValue覆盖它,否则它会单独保存它。
  3. removes :最后,它检查值为23的键23和23的值中的键1的值是否小于removingValue,如果键23的hashMap在删除后为空,则从主映射中删除键23。

我测试了这段代码。例如:

  • 对于3、10、5,<23,1>的最终值是10。
  • 对于20,10,11,最终值是20。
  • 对于3、10、11,最终值为空,因为条目23被删除。

我希望它是线程安全的,因为我刚才使用了合并方法。这段代码的一个缺点是,我正在添加要映射的内容,然后删除它,仅仅因为ConcurrentHashMap没有类似于合并的删除方法。我希望我有这样的方法:

map.remove (keyToRemove,条件)

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/38494042

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档