类似地,想要不受打扰地迭代容器元素,我们也要在 for 循环的外面加锁,但是可能并不是一个好的主意 。假如容器的规模非常大,或者每个元素的处理时间非常长,那么其它等待容器执行短作业的线程会因此陷入长时间的等待,这会带来活跃性问题 。
一个可行的方法就是实现读写分离 —— 一旦有写操作,则重新拷贝一份新的容器副本,而在此期间所有读操作则仍在原来的容器中进行,实现 "读-读共享" 。当读操作远多于写操作时,这种做法无疑可以大幅度地提高程序的吞吐量,见后文的并发容器 CopyOnWriteArrayList 。
警惕隐含迭代的操作不仅是显式的 for 循环会触发迭代 。比如容器的 toString 方法在底层调用 StringBuilder.Append() 方法依次将每一个元素的字符串拼接起来 。除此之外,包括 equals , containsAll , removeAll , retainAll ,乃至将容器本身作为参数的构造器,都隐含了对容器的迭代过程 。这些间接的迭代错误都有可能抛出
ConcurrentModificationException 异常 。
并发容器【Java 同步工具与组合类的线程安全性分析】考虑到重量级锁对性能的影响,Java 后续提供了各种并发容器来改进同步容器的性能问题 。同步容器将所有操作完全串行化 。当锁竞争尤其激烈时,程序的吞吐量将大大降低 。因此,使用并发容器来替代同步容器,在绝大部分情况下都算是一顿 "免费的午餐" 。
ConcurrentHashMapConcurrentHashMap 使用了更小的封锁粒度换取了更大程度的共享,这个封锁机制称之为分段锁 ( Lock Stripping ) 。简单点说,就是每一个桶由单独的锁来保护,操作不同桶的两个线程不需要相互等待 。好处是,在高并发环境下, ConcurrentHashMap 带来了更大的吞吐量,但问题是,封锁粒度的减小削弱了容器的一致性语义,或称弱一致性 ( Weakly Consistent ) 。
比如说需要在整个 Map 上计算的 size() 和 isEmpty() 方法,弱一致性会使得这些方法的计算结果是一个过期值 。这考虑到是一个权衡,因为在并发环境下,这两个方法的作用很小,因为其返回值总是不断变化的 。因此,这些操作的需求被弱化了,以换取其它更重要的性能优化,比如 get , put , cotainsKey , remove 等 。
因此,除非一部分严谨的业务无法容忍弱一致性,否则并发的 HashMap 是要比同步 HashMap 更优的选择 。
CopyOnWriteArrayList该工具在读操作远多于写操作的场合下能够提供更好的并发性能,在迭代时不需要对容器进行加锁或者复制 。当发生修改时,该容器会创建并重新发布一个新的容器副本 。在新副本创建之前,一切读操作仍然以旧的容器为准,因此这不会抛出
ConcurrentModificationException 问题 。
相对的,如果频繁调用 add , remove , set 等方法,则该容器的吞吐量会大大降低,因为这些操作需要反复调用系统的 copy 方法复制底层的数组 ( 这也是没有设计 "CopyOnWriteLinkedList" 的原因,因为拷贝的效率会更低 ) 。同时,写入时复制的特性使得 CopyOnWriteArrayList 是弱一致性的 。
阻塞队列 & 生产者 — 消费者模式阻塞队列,简单地说,就是当队列为空时,执行 take 操作会进入阻塞状态;当队列满时,执行 put 操作也会进入阻塞状态 。阻塞队列也可以分有界队列和无界队列 。无界队列永远不会充满,因此执行 put 方法永远不会进入阻塞状态 。但是,如果生产者的执行效率远超过消费者,那么无界队列的无限扩张最终会耗尽内存 。有界队列则可以保证当队列充满时,生产者被 put 阻塞,通过这种方式来让消费者赶上工作进度 。
可以用阻塞队列实现生产者 — 消费者模式,最常见的生产者 — 消费者模式是线程池与工作队列的组合 。这种模式将 "发布任务" 与 "领取任务" 解耦,最大的便捷是简化了复杂的负载管理,因为生产者和消费者的执行速度并不总是相匹配的 。同时,生产者和消费者的角色是相对的 。比如处于流水线中游的组件,它们既作为上游的消费者,也作为下游的生产者 。
Java 库已经包含了关于阻塞队列的多种实现,它自身保证 put 和 take 操作是线程安全的 。
- LinkedBlockingQueue 和 ArrayBlockingQueue :此两者的区别可以参考 Link 和 Array,见: ArrayBlockingQueue 和 LinkedBlockingQueue。两者均为 FIFO 的队列 。
推荐阅读
- 使用 JavaScript 实现无限滚动
- 如何自己开发漏洞扫描工具
- Firefox浏览器|火狐浏览器新增实用工具:将支持图片文字识别
- Java|Java:2022年招聘Java开发人员指南
- Java|HR傲慢对待求职者,还“诅咒”对方找不到工作,大学生也太难了
- 什么资源都能搜?这款搜索引擎工具,一键检索各大网盘资源
- 在Java 8及更高版本中使用Java流
- Java实现第三方短信接口发送短信验证码
- 3个超强资源搜索工具 资源搜索工具
- 连裤袜|“颜值经济”盛行,美妆工具受追捧,我国美妆工具市场需求增势明显
