HashTable 同步方法和竞争条件
HashTable synchronized methods and race conditions
我想在这里掌握 Collections,并在 Race condition
找到了一篇文章
声明以下代码可能导致竞争条件:
if (! hashtable.contains(key)) {
hashtable.put(key, value);
}
里面的解释我理解了一定程度,但是我有疑问,既然HashTable的方法是同步的,怎么可能thread1在执行put()方法的时候获取了整个Hashtable的锁,其他thread2可以执行containskey()方法吗?这两个方法不是在哈希表锁上同步的吗?
HashTable
上的同步是针对每个方法的:仅在方法调用期间获取和持有锁。
这里的代码相当于:
boolean contains;
synchronized (hashtable) {
contains = hashtable.contains(key);
}
if (!contains) {
synchronized (hashtable) {
hashtable.put(key, value);
}
}
另一个线程完全有可能在这两个同步块之间交错,导致竞争条件。
值得注意的是,HashTable
实际上已被弃用,仅出于遗留原因而保留。更现代的替代方案是 Collections.synchronizedMap(new HashMap<>())
(从同步的角度来看其行为基本相同;see this question)或 ConcurrentHashMap
,它提供了自动更新地图的方法,例如
concurrentHashMap.putIfAbsent(key, value);
Java 8 map确实提供了computeIfAbsent
,但是不保证是原子的,除了在ConcurrentHashMap
.
how is it possible that while thread1 is executing the put()
method acquiring the lock on the entire Hashtable, other thread2 can execute the containskey()
method?
这不可能。这里的问题与 Hashtable
class 本身无关,正如您指出的那样,它是同步的 class 因此不会被破坏。竞争条件是 thread-A 可能会测试不存在的密钥。但在它可以执行 put 之前,thread-B 测试仍然不存在的相同密钥。然后两个线程将相同的值放入 table 中,其中一个覆盖另一个。这就是比赛。
1 if (! hashtable.contains(key)) {
2 hashtable.put(key, value);
3 }
更完整地列举种族:
- Thread-A 调用
hashtable.contains("foo")
,第 1 行 returns 错误。
- Thread-B 运行s 然后还会调用
hashtable.contains("foo")
,这在第 1 行也是 returns false。
- Thread-A 在第 2 行调用
hashtable.put("foo", "bar")
。
- Thread-B 在第 2 行调用
hashtable.put("foo", "baz")
。
如果按此顺序发生(当然可以),则 Thread-B 将覆盖 Thread-A 的 "foo"
值。 Hashtable
未损坏,但代码逻辑可能不希望覆盖。也可能发生 4 和 3 颠倒的情况,因此 Thread-A 会覆盖 Thread-B 的 "foo"
值。竞争条件的本质是线程 运行 顺序无法预测,要确保正确的逻辑,您需要应用特定的锁定。
顺便说一句,Hashtable
是旧的 class,已被 ConcurrentHashMap
取代。但是 ConcurrentHashMap
的竞争条件仍然存在,尽管它 确实 具有像 concurrentMap.putIfAbsent(key, value)
for this reason. See ConcurrentMap
javadocs.
这样的原子操作
我想在这里掌握 Collections,并在 Race condition
找到了一篇文章声明以下代码可能导致竞争条件:
if (! hashtable.contains(key)) {
hashtable.put(key, value);
}
里面的解释我理解了一定程度,但是我有疑问,既然HashTable的方法是同步的,怎么可能thread1在执行put()方法的时候获取了整个Hashtable的锁,其他thread2可以执行containskey()方法吗?这两个方法不是在哈希表锁上同步的吗?
HashTable
上的同步是针对每个方法的:仅在方法调用期间获取和持有锁。
这里的代码相当于:
boolean contains;
synchronized (hashtable) {
contains = hashtable.contains(key);
}
if (!contains) {
synchronized (hashtable) {
hashtable.put(key, value);
}
}
另一个线程完全有可能在这两个同步块之间交错,导致竞争条件。
值得注意的是,HashTable
实际上已被弃用,仅出于遗留原因而保留。更现代的替代方案是 Collections.synchronizedMap(new HashMap<>())
(从同步的角度来看其行为基本相同;see this question)或 ConcurrentHashMap
,它提供了自动更新地图的方法,例如
concurrentHashMap.putIfAbsent(key, value);
Java 8 map确实提供了computeIfAbsent
,但是不保证是原子的,除了在ConcurrentHashMap
.
how is it possible that while thread1 is executing the
put()
method acquiring the lock on the entire Hashtable, other thread2 can execute thecontainskey()
method?
这不可能。这里的问题与 Hashtable
class 本身无关,正如您指出的那样,它是同步的 class 因此不会被破坏。竞争条件是 thread-A 可能会测试不存在的密钥。但在它可以执行 put 之前,thread-B 测试仍然不存在的相同密钥。然后两个线程将相同的值放入 table 中,其中一个覆盖另一个。这就是比赛。
1 if (! hashtable.contains(key)) {
2 hashtable.put(key, value);
3 }
更完整地列举种族:
- Thread-A 调用
hashtable.contains("foo")
,第 1 行 returns 错误。 - Thread-B 运行s 然后还会调用
hashtable.contains("foo")
,这在第 1 行也是 returns false。 - Thread-A 在第 2 行调用
hashtable.put("foo", "bar")
。 - Thread-B 在第 2 行调用
hashtable.put("foo", "baz")
。
如果按此顺序发生(当然可以),则 Thread-B 将覆盖 Thread-A 的 "foo"
值。 Hashtable
未损坏,但代码逻辑可能不希望覆盖。也可能发生 4 和 3 颠倒的情况,因此 Thread-A 会覆盖 Thread-B 的 "foo"
值。竞争条件的本质是线程 运行 顺序无法预测,要确保正确的逻辑,您需要应用特定的锁定。
顺便说一句,Hashtable
是旧的 class,已被 ConcurrentHashMap
取代。但是 ConcurrentHashMap
的竞争条件仍然存在,尽管它 确实 具有像 concurrentMap.putIfAbsent(key, value)
for this reason. See ConcurrentMap
javadocs.