我知道Hashtable
已同步,但为什么get()
方法已同步?
它只是一种读取方法吗?
答案 0 :(得分:15)
如果读取未同步,则可以在执行读取期间修改Hashtable。可以添加新元素,底层数组可能变得太小,可能会被更大的元素替换,等等。如果没有顺序执行,很难处理这些情况。
但是,即使Hahtable被另一个线程修改时get
也不会崩溃,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获取新数据。
我们如何执行它?每次线程进入并离开同步块时,都会执行隐式memory barrier。内存屏障强制更新缓存。但是,要求写入者和阅读者都必须执行内存屏障。否则,信息传达不正确。
在我们的示例中,线程B已经使用了同步方法set
,因此在方法结束时传递其数据修改。但是,A没有看到修改后的数据。解决方案是使get
同步,因此它被迫获取更新的数据。
答案 1 :(得分:1)
查看Hashtable源代码,您可以想到许多可能导致非同步get()
问题的竞争条件。
(我正在阅读JDK6源代码)
例如,rehash()
将创建一个空数组,并将其分配给实例var table
,并将旧表中的条目放入新表中。因此,如果您的get
发生在空数组赋值之后,但在实际将条目放入其中之前,即使它在表中也找不到您的密钥。
另一个例子是,循环迭代通过表索引处的链表,如果在迭代中间,则重新发生。即使它存在于哈希表中,您也可能无法找到该条目。
答案 2 :(得分:0)
Hashtable
已同步,意味着整个类是线程安全的
在Hashtable
内,不仅get()方法已同步,而且还有许多其他方法。特别是put()方法就像Tom说的那样同步。
必须将read方法同步为write方法,因为它将确保变量的可见性和一致性。