在诸如不鼓励之类的事情之后,Hashtable
以及Vector
当集合同步包装器出现时,我认为同步会得到更有效的处理。现在我查看了代码,我很惊讶它实际上只是用同步块包装了集合。
为什么ReadWriteLock
s 不包含在例如 Collections 中的 SynchronizedMap 中?是否存在一些不值得的效率考虑?
在诸如不鼓励之类的事情之后,Hashtable
以及Vector
当集合同步包装器出现时,我认为同步会得到更有效的处理。现在我查看了代码,我很惊讶它实际上只是用同步块包装了集合。
为什么ReadWriteLock
s 不包含在例如 Collections 中的 SynchronizedMap 中?是否存在一些不值得的效率考虑?
读写锁是性能优化的一部分,这意味着它可以在某些情况下允许更大的并发性。必要条件是,它们应用于大多数时间读取但未修改的数据结构。
在其他条件下,它们的性能比独占锁稍差,这是很自然的,因为它们具有更大的复杂性。
如果读写锁的锁通常保持相当长的时间并且只对受保护的资源进行少量修改,则它是最有效的。
因此,读写锁是否优于独占锁取决于用例。最终,您必须通过分析来衡量哪些锁性能更好。
考虑到这一点,选择一个排他锁来Collections.synchronizedMap
解决一般用例而不是大多数读者的特殊情况似乎是合适的。
更多链接
他们编写了一个工具,可以将 Java 应用程序中的锁自动转换为适当ReentrantLocks
的ReadWriteLocks
位置。出于测量目的,他们还提供了一些有趣的基准测试结果:
[...] 但是,在写入操作更为普遍的配置中,具有同步块的版本比使用 Sun 1.6.0_07 JVM 的基于读写锁的版本快 50%(使用 Sun 1.5 快 14%。 0_15 JVM)。
在只有 1 个读取器和 1 个写入器的低争用情况下,性能差异不那么极端,并且三种类型的锁中的每一种都在至少一个机器/VM 配置上产生了最快的版本(例如,请注意 ReentrantLocks 在Sun 1.5.0_15 JVM 的 2 核机器)。
我不认为使用ReadWriteLock
(如果这就是你所说的)比使用synchronized
关键字更快。两种构造都施加了锁,竖立了内存屏障,并“发生在”限制。
您可能正在谈论做一些聪明的事情Collections.synchronizedMap(...)
和朋友,其中读取方法被读取锁定并且写入方法写入锁定以提高性能。这可能适用于java.util
集合类,但可能会导致与用户实现的 s 同步问题,Map
如果get()
方法被计数或其他东西 - 即“只读”方法实际上对集合进行了更新。是的,这样做将是一个糟糕的主意。
ConcurrentHashmap
被编写为高性能并volatile
直接使用字段而不是synchronized
块。相比而言,这使得代码更加复杂,Collections.synchronizedMap(...)
但速度也更快。这就是为什么建议在高性能情况下使用它的原因Collections.synchronizedMap(new HashMap<...>())
。
除以下内容外,大多数推理都已解决。SynchronizedMap/Set/List 以及 Hashtable 和 Vector 依赖于正在同步的集合实例。因此,许多开发人员使用这种同步来确保原子性。例如。
List syncList = Collections.synchronizedList(new ArrayList());
//put if absent
synchronized(syncList){
if(!syncList.contains(someObject)){
syncList.add(someObject);
}
}
这对于所有操作都是线程安全和原子的,因为 synchronizedList 将自行同步(即添加、删除、获取)。这就是为什么 Hashtable 类没有被改造以支持类似于 ConcurrentHashMap 的 lock-striping 的主要原因。
因此,对这些集合使用 ReadWriteLock 将失去原子操作的能力,除非您能够获取锁实例。
这是因为这些类早于读/写锁,后者进入 Java 相当晚(Java 5)。无论如何,由于它们硬编码的细粒度锁定,它们很少有用。
这与效率无关。synchronized
在中使用没有错Collections.synchronizedMap
。如果使用 ReadWriteLock 来实现 Map,我想调用它 Collections.LockedMap
;)
严肃点,是在出现Collections.synchronizedMap
前几年写的。Lock
这是一个在发布后无法更改的 API。
要添加到@JohnVint 的答案,请考虑迭代问题。该文档明确要求客户端同步:
用户在迭代返回的列表时必须手动同步它:
List list = Collections.synchronizedList(new ArrayList()); ... synchronized (list) { Iterator i = list.iterator(); // Must be in synchronized block while (i.hasNext()) foo(i.next()); }
不遵循此建议可能会导致不确定的行为。
这只是因为客户端可以共享返回的地图上的内在锁。如果要在内部使用读/写锁,则返回的接口必须支持某种方式,至少可以访问读锁以实现安全迭代。这将使 API 变得复杂,从而产生可疑的好处(正如其他人所解释的那样)。