JAVA-并发和锁

JAVA-并发和锁,java,multithreading,locking,Java,Multithreading,Locking,我需要创建如下哈希映射: Map<String, List<Integer>> for (String key : keys) { map.put(key, Collections.synchronizedList(new ArrayList<>())); } List<Integer> list = map.get(key); if (list == null) { map.putIfAbsent(key, Collectio

我需要创建如下哈希映射:

Map<String, List<Integer>>
for (String key : keys) {
    map.put(key, Collections.synchronizedList(new ArrayList<>()));
}
List<Integer> list = map.get(key);
if (list == null) {
    map.putIfAbsent(key, Collections.synchronizedList(new ArrayList<>()));
    list = map.get(key);
}
两个线程都有助于创建条目;因此,例如,线程1要存储以下数据集:

-----------------------------------
| 1 |  (xxx,a)  (yyy,a')  (zzz,A) |
| 2 |  (xxx,b)  (yyy,b')  (zzz,B) |
-----------------------------------
线程2具有:


因此,在同一时刻,两个线程可以计算相同的密钥:线程1(xxx,a)和线程2(xxx,c)

我能使用的最好的锁定/解锁机制是什么

我试了下一个

ReadWriteLock lock = new ReentrantReadWriteLock();

executor.submit(() -> {
   lock.writeLock().lock();
   try {
      sleep(1);
      map.put("foo", "bar");
  } finally {
     lock.writeLock().unlock();
  }
}))

但我不想锁定整个哈希映射。我想创建一个锁,当且仅当处理过程中的两个线程在同一个密钥上工作时


谢谢

您应该使用Hashtable而不是Hashmap。哈希表是线程安全的(已同步),无需再次实现锁定/解锁机制。

不确定另一个答案是否符合您的要求,因为您需要从映射中读取(查看密钥是否已存在),然后在原子操作中更新

不确定这是否是最优雅的解决方案,但您可以使用这样的解决方案

导入java.util.HashSet; 导入java.util.Map; 导入java.util.Set; 导入java.util.concurrent.ConcurrentHashMap

public class KeyLockMap<K, V> {

    private Map<K, V> map;

    private Set<K> lockKeys;

    public KeyLockMap() {
        map = new ConcurrentHashMap<K, V>();
        lockKeys = new HashSet<K>();
    }

    public void lock(K key) throws InterruptedException {
        synchronized(lockKeys) {
            // Keep waiting while the lockKeys set contains the key we want to update
            while(lockKeys.contains(key)) {
                lockKeys.wait();
            }
        }
    }

    public void unlock(K key) {
        synchronized(lockKeys) {
            lockKeys.remove(key);
            // Notify any threads that may be waiting for this key to be removed
            lockKeys.notifyAll();
        }
    }

    public Map<K, V> getMap() {
        return map;
    }
}
公共类密钥锁映射{
私人地图;
私用锁匙;
公钥锁映射(){
map=新的ConcurrentHashMap();
lockKeys=newhashset();
}
公共无效锁(K键)引发中断异常{
同步(锁匙){
//lockKeys集合包含我们要更新的密钥,请继续等待
while(锁钥匙。包含(钥匙)){
锁钥匙。等等();
}
}
}
公共无效解锁(K密钥){
同步(锁匙){
锁钥匙。取下(钥匙);
//通知可能正在等待删除此密钥的所有线程
lockKeys.notifyAll();
}
}
公共地图getMap(){
返回图;
}
}
现在在你的线程中,你可以

keyLockMap.lock(KEY);
Map<String, List<Integer>> map = keyLockMap.getMap();
...do updates...
keyLockMap.unlock(KEY);
keyLockMap.lock(钥匙);
Map Map=keyLockMap.getMap();
…做更新。。。
钥匙锁地图。解锁(钥匙);
希望这有帮助


另一种可行的方法是在ConcurrentHashMap上使用“putIfAbsent”,例如

List<Integer> newValue = new CopyOnWriteArrayList<>();
List<Integer> value = concurrentMap.putIfAbsent(KEY, newValue);

if(value == null) {
    value = newValue;
}

value.add(DATA);
List newValue=newcopyonwritearraylist();
列表值=concurrentMap.putIfAbsent(键,newValue);
如果(值==null){
值=新值;
}
增值(数据);
如果另一个线程先执行
putIfAbsent
,您将获得该线程创建的
CopyOnWriteArrayList
,并且您将能够向其中添加数据,因为
CopyOnWriteArrayList
是线程安全的


(编辑以解释第一次放置时返回的null…

无需重新发明轮子,
java.util.concurrent
包已经包含
Map
接口的线程安全实现

但是,您还必须同步您的列表(请参阅下面的BretC评论)。因此,最方便的解决方案可能是使用。就像这样:

public static void main(String[] args) {

    Multimap <String, Integer> map = Multimaps.synchronizedMultimap(HashMultimap.<String, Integer> create());

    // In Thread 1
    map.put("foo", 1);

    //In Thread 2
    map.put("foo", 2);

    System.out.println(map); // prints {foo=[1, 2]}
}
publicstaticvoidmain(字符串[]args){
Multimap=Multimaps.synchronizedMultimap(HashMultimap.create());
//在线程1中
地图.put(foo,1);;
//在线程2中
地图.put(foo,2);;
System.out.println(map);//prints{foo=[1,2]}
}

首先,您应该使用并发映射:

Map<String, List<Integer>> map = new ConcurrentHashMap<>();
Map Map=new ConcurrentHashMap();
其次,在多线程访问之前,您应该使用同步列表填充它,如下所示:

Map<String, List<Integer>>
for (String key : keys) {
    map.put(key, Collections.synchronizedList(new ArrayList<>()));
}
List<Integer> list = map.get(key);
if (list == null) {
    map.putIfAbsent(key, Collections.synchronizedList(new ArrayList<>()));
    list = map.get(key);
}
for(字符串键:键){
map.put(key,Collections.synchronizedList(newarraylist());
}
您可以像这样及时完成:

Map<String, List<Integer>>
for (String key : keys) {
    map.put(key, Collections.synchronizedList(new ArrayList<>()));
}
List<Integer> list = map.get(key);
if (list == null) {
    map.putIfAbsent(key, Collections.synchronizedList(new ArrayList<>()));
    list = map.get(key);
}
List List=map.get(键);
if(list==null){
map.putIfAbsent(key,Collections.synchronizedList(newArrayList());
list=map.get(key);
}
就这样。现在一切都应该是线程安全的


注意:在从地图中获取列表之前,使用列表非常重要。由于并发性,多个线程可能会尝试向映射添加缺少的列表,但只有一个线程会成功。因此,在尝试添加缺少的列表后,始终从映射中获取它。

那么,我是否可以将相同的(共享的)数据结构传递给两个线程,以便它们中的每个线程都可以存储自己的部分数据?如果使用java 8,您可能只需要使用
ConcurrentHashMap
putIfAbsent
,然后使用CopyOnWrite列表作为值……请更好地解释一下CopyOnWrite步骤?Java中引入的ConcurrentHashMap7@Panner实际上在Java5中:)@BretC
value=concurrentMap.putIfAbsent(KEY,value)返回
null
。因此,下一个
value.add(DATA)
可能会失败。如果线程1读取映射以查看键是否存在(它不存在),然后线程2读取映射以查看是否存在相同的键(它仍然不存在),会发生什么情况。。。一个线程的put不会覆盖另一个线程吗?如果您事先不知道密钥是什么,会发生什么情况?您也可以使用
map.putIfAbsent()
,跳过初始化阶段。但每次访问值之前都必须这样做。但是您可以通过尝试获取来进行优化,只有在获取失败的情况下,
putIfAbsent()
。我为您提到的备选方案添加了示例代码,事先不知道密钥。@Harmlezz正如您在上一个答案中所写的,一个
列表。添加(值)
应该在
if
语句之外完成,这不是真的吗?@Fab我不知道你的意思<代码>列表.添加()
可以在我发布所有代码后安全地完成。您可以在获得存储在地图中的列表后添加某些内容,而不是之前。如果其他线程之前添加了一个缺少的列表,则列表的双重获取非常重要。