为什么Java的同步集合不使用读/写锁?
在Hashtable
和Vector
类的东西不鼓励之后,当Collections同步包装器出现时,我认为同步将更有效地处理。 现在我查看了代码,我很惊讶它只是用同步块包装集合。
为什么ReadWriteLock
不包含在集合中的SynchronizedMap中? 是否存在一些不值得的效率考虑因素?
读写锁是性能优化的一部分,这意味着它可以在某些情况下允许更大的并发性。 必要条件是,它们应用于大多数时间读取但未修改的数据结构。
在其他条件下,它们的表现略差于独占锁,这是自然的,因为它们具有更大的复杂性。
如果读写锁的锁通常保持适度长时间并且对受保护资源仅进行少量修改,则效率最高。
因此,读写锁是否优于排它锁取决于用例。 最终,您必须使用分析来衡量哪些锁具有更好的性能。
考虑到这一点,为Collections.synchronizedMap
选择一个解决一般用例的独占锁而不是大多数读者的特殊情况似乎是合适的。
更多链接
- 使用ReadWriteLocks进行饥饿
-
重构Java程序以实现灵活锁定
他们编写了一个工具,可以在适当的情况下自动将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
集合类,但是如果正在计算get()
方法或者某些东西,可能会导致用户实现的Map
的同步问题 – 即“只读”的方法实际上对集合。 是的,这样做是一个糟糕的主意。
ConcurrentHashmap
被编写为高性能并直接使用volatile
字段而不是synchronized
块。 与Collections.synchronizedMap(...)
相比,这使得代码更加复杂,但也更快。 这就是为什么在Collections.synchronizedMap(new HashMap<...>())
推荐用于高性能情况的原因。
除以下内容外,大多数推理都已得到解决。 SynchronizedMap / Set / List以及Hashtable和Vector依赖于正在同步的集合实例。 因此,许多开发人员使用此同步来确保primefaces性。 例如。
List syncList = Collections.synchronizedList(new ArrayList()); //put if absent synchronized(syncList){ if(!syncList.contains(someObject)){ syncList.add(someObject); } }
这是所有操作的线程安全和primefaces,因为synchronizedList将自己同步(即添加,删除,获取)。 这就是为什么Hashtable类没有被改装以支持类似于ConcurrentHashMap的锁定条带化的主要原因。
因此,对于这些集合使用ReadWriteLock会失去primefaces操作的能力,除非您能够获取锁定实例。
这是因为这些类早于读/写锁,后来很晚才进入Java(Java 5)。 无论如何,它们很少有用,因为它们具有硬编码的细粒度锁定。
这与效率无关。 在Collections.synchronizedMap
使用synchronized
是没有错的。 如果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变得更加复杂(正如其他人所解释的那样)。