我知道 aHashtable
是同步的,但为什么它的get()
方法是同步的?
它只是一种读取方法吗?
如果读取不同步,则可以在读取执行期间修改 Hashtable。可能会添加新元素,底层数组可能会变得太小而被更大的数组替换,等等。如果没有顺序执行,很难处理这些情况。
但是,即使get
Hashtable 被另一个线程修改时不会崩溃,synchronized
关键字还有另一个重要方面,即缓存同步。让我们使用一个简化的例子:
class Flag {
bool value;
bool get() { return value; } // WARNING: not synchronized
synchronized void set(bool value) { this->value = value; }
}
set
是同步的,但get
不是。如果两个线程 A 和 B 同时读写这个类会发生什么?
1. A calls read
2. B calls set
3. A calls read
是否保证在步骤 3 A 看到线程 B 的修改?
不,它不是,因为 A 可能在不同的核心上运行,它使用一个单独的缓存,旧值仍然存在。因此,我们必须强制 B 将内存与其他内核通信,并强制 A 获取新数据。
我们如何执行它?每次,一个线程进入和离开一个同步块,都会执行一个隐式内存屏障。内存屏障强制更新缓存。但是,要求写入者和读取者都必须执行内存屏障。否则,信息无法正确传达。
在我们的示例中,线程 B 已经使用了同步方法set
,因此它的数据修改在方法结束时进行通信。但是,A 看不到修改后的数据。解决办法是get
同步,所以强制获取更新的数据。
查看 Hashtable 源代码,您会想到许多可能导致未同步get()
.
(我正在阅读JDK6源代码)
例如, arehash()
将创建一个空数组,并将其分配给实例 var table
,并将旧表中的条目放入新表中。因此,如果您get
发生在空数组分配之后,但在实际将条目放入其中之前,即使它在表中,您也无法找到您的键。
另一个例子是,有一个循环遍历表索引处的链表,如果在你的迭代中间,就会发生 rehash。即使它存在于哈希表中,您也可能无法找到该条目。
Hashtable
是同步的,意味着整个类是线程安全的
在 内部Hashtable
,不仅 get() 方法是同步的,许多其他方法也是同步的。尤其是 put() 方法是同步的,就像汤姆说的那样。
读取方法必须与写入方法同步,因为它将确保变量的可见性和一致性。