0

下面的代码有什么问题?

private Map<Integer, Integer> aMap = new ConcurrentHashMap<Integer, Integer>();    
Record rec = records.get(id);
  if (rec == null) {
      rec = new Record(id);
      records.put(id, rec);
  }
  return rec;
  1. 上面的代码不是线程安全的吗?在这种情况下我为什么要putIfAbsent在这里使用?
  2. 锁定仅适用于更新。在检索的情况下,它允许完全并发。这个说法是什么意思?
4

2 回答 2

6

它不是线程安全的。

  1. 如果有另一个线程,那么在这之间的时间里,records.get另一个records.put线程可能也放了记录。

  2. 只读操作(即不修改结构的操作)可以由多个线程同时完成。例如,1000 个线程可以安全地读取int. int但是,如果没有某种锁定操作,那 1000 个线程无法更新 的值。

我知道这听起来像是一个非常不可能的事件,但请记住,百万分之一的事件在 1GHz 下每秒发生 1000 次。


这是线程安全的:

private Map<Integer, Integer> aMap = new ConcurrentHashMap<Integer, Integer>();
// presumably aMap is a member and the code below is in a function
aMap.putIfAbsent(id, new Record(id))
Record rec = records.get(id);
return rec;

请注意,这可能会创建一个Record并且永远不会使用它。

于 2013-09-29T00:30:48.583 回答
3

它可能是线程安全的,也可能不是线程安全的,这取决于您希望它如何工作。

在代码结束时,aMap将安全地拥有一个Recordfor id。但是,两个线程可能会同时创建并放入一个Record,因此存在两个(或更多,如果有更多线程)Records。这可能很好,也可能不是——真的取决于您的应用程序。

线程安全的危险之一(例如,如果您使用HashMap没有同步的正常)是线程可以跨线程读取部分创建或部分更新的对象。换句话说,事情可能会变得非常混乱。这不会在您的代码中发生,因为ConcurrentHashMap将确保内存在线程之间保持最新,并且从这个意义上说它是线程安全的。

您可以做的一件事是使用putIfAbsent,它将以原子方式将键值对放入映射中,但前提是该键已经没有任何内容:

if (rec == null) {
    records.putIfAbsent(id, new Record(id));
    rec = records.get(id);
}

在这种方法中,您可能会创建第二个Record对象,但如果是这样,它不会被插入,并且会立即用于垃圾回收。在片段的末尾:

  • records将包含Record给定 id 的
  • 只有一个Record会被放入records该ID(无论是由这个线程还是另一个)
  • rec将指向该记录
于 2013-09-29T00:48:13.763 回答