我已经从多个线程阅读了有关Java数组元素可见性的一些问题和答案,但是我仍然无法真正解决某些情况。为了演示我遇到的问题,我提出了一个简单的场景:假设我有一个简单的集合,该集合通过将元素散列为一个将其添加到n个存储桶之一中(存储桶类似于某种列表) 。并且每个存储桶分别同步。例如:
private final Object[] locks = new Object[10];
private final Bucket[] buckets = new Bucket[10];
在这里,一个水桶i
应该被守卫lock[i]
。这是添加元素代码的样子:
public void add(Object element) {
int bucketNum = calculateBucket(element); //hashes element into a bucket
synchronized (locks[bucketNum]) {
buckets[bucketNum].add(element);
}
}
由于“存储桶”是最终的,因此即使没有同步也不会出现任何可见性问题。我的猜测是,使用同步,没有最终版本也不会出现任何可见性问题,这是正确的吗?
最后,比较棘手的部分。假设我想从任意线程中复制并合并所有存储桶的内容,并清空整个数据结构,如下所示:
public List<Bucket> clear() {
List<Bucket> allBuckets = new List<>();
for(int bucketNum = 0; bucketNum < buckets.length; bucketNum++) {
synchronized (locks[bucketNum]) {
allBuckets.add(buckets[bucketNum]);
buckets[bucketNum] = new Bucket();
}
}
return allBuckets;
}
我基本上将旧的存储桶换成新创建的存储桶,然后返回旧的存储桶。这种情况与add()
我们的情况有所不同,因为我们没有修改数组中引用所引用的对象,而是直接更改了数组/引用。
请注意,当我持有存储桶1的锁时,我并不关心存储桶2是否被修改,我不需要结构完全同步和一致,仅可见性和接近一致性就足够了。
因此,假设bucket[i]
仅在下修改了所有lock[i]
代码,您会说这段代码有效吗?我希望能够了解为什么以及为什么不能,并更好地了解知名度,谢谢。
第一个问题。
在这种情况下,线程安全性取决于是否正确共享对包含locks
和的对象的引用buckets
(我们称之为Container
)。
试想:一个线程繁忙实例化一个新的Container
对象(分配内存,实例化阵列等),而另一个线程开始使用这种半实例化对象,其中locks
并buckets
仍然空(他们没有被第一个线程实例还)。在这种情况下,此代码:
synchronized (locks[bucketNum]) {
摔坏并抛出NullPointerException
。该final
关键字防止这一点,并保证由当时的参考Container
不为空,其最终的领域已经被初始化:
对象的构造函数完成后,就认为该对象已完全初始化。保证只有在对象完全初始化之后才能看到对对象的引用的线程才能确保看到该对象的最终字段的正确初始化值。(JLS 17.5)
第二个问题。
假设locks
and buckets
字段是最终的,并且您不关心整个数组的一致性,并且 “每个bucket [i]仅在lock [i]下进行了修改”,那么这段代码就可以了。
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句