Sun*_*ini 3 multithreading synchronization kotlin
我们正在尝试存储特定键的唯一对象。当在多线程环境中调用 getMyObject 时,我们在 return 语句时收到 null ptr 异常
object SampleClass
{
fun getMyObject(Id : String) : MyObject
{
if(!myMap.containsKey(Id))
{
synchronized(SampleClass)
{
if(!myMap.containsKey(Id))
{
myMap[Id] = MyObject()
}
}
}
return myMap[Id]!!
}
private val myMap = HashMap<String,MyObject>()
}
Run Code Online (Sandbox Code Playgroud)
看起来即使 contains 方法返回 true,当我们尝试获取该值时,该值仍返回 null。我不确定其背后的原因是什么。
如果你试图超越内存模型,就会遇到这种麻烦。如果你查看 的HashMap源代码,你会发现containsKey实现如下:
public boolean containsKey(Object key) {
return getNode(key) != null;
}
Run Code Online (Sandbox Code Playgroud)
请注意,仅当存在HashMap.Node与给定键对应的对象时,它才返回 true。现在,这是如何get实现的:
public V get(Object key) {
Node<K,V> e;
return (e = getNode(key)) == null ? null : e.value;
}
Run Code Online (Sandbox Code Playgroud)
您所看到的是不安全发布问题的一个实例。假设 2 个线程(A 和 B)调用getMyObject一个不存在的密钥。synchronizedA 稍微领先于 B,因此它在 B 调用 之前进入块containsKey。特别是,Aput在 B 之前调用containsKey。调用put创建一个新Node对象并将其放入哈希映射的内部数据结构中。
containsKey现在,考虑 B在 A 存在该块之前调用的情况synchronized。B可能会看到NodeA 放置的对象,在这种情况下containsKey返回 true。但此时该节点是不安全发布的,因为它被B以非同步方式并发访问。不能保证它的构造函数(设置其value字段的构造函数)已被调用。即使它被调用,也不能保证value引用(或构造函数设置的任何引用)与节点引用一起发布。这意味着 B 可以看到不完整的节点:节点引用,但看不到其值或其任何字段。当 B 进行到 时get,它读取null为不安全发布节点的值。因此NullPointerException。
这是一个用于可视化这一点的临时图表:
Thread A Thread B
- Enter the synchronized block
- Call hashMap.put(...)
- Insert a new Node
- See the newly inserted (but not yet
initialized from the perspective of B)
Node in HashMap.containsKey
- Return node.value (still null)
from HashMap.get
- !! throws a `NullPointerException`
...
- Exit the synchronized block
(now the node is safely published)
Run Code Online (Sandbox Code Playgroud)
以上只是可能出现问题的一种情况(请参阅评论)。为了避免这种危险,要么使用ConcurrentHashMap(例如),要么永远不要在块之外同时map.computeIfAbsent(key, key -> new MyObject())访问您的。HashMapsynchronized
| 归档时间: |
|
| 查看次数: |
156 次 |
| 最近记录: |