【问题标题】:Is java concurrent package implemented using locks?java并发包是用锁实现的吗?
【发布时间】:2014-10-24 12:00:52
【问题描述】:

从概念上来说,

Mutex

Reader's/Writer lock (Better form of Mutex)

Semaphore

Condition Variable

被用作四种主要的同步机制,它们是纯粹基于锁的。对于这 4 种机制,不同的编程语言有不同的术语/行话。 POSIX pthread 包就是这种实现的一个例子。

前两个使用自旋锁(Busy-wait)实现。

最后两个使用睡眠锁实现。

基于锁的同步在 cpu 周期方面是昂贵的。

但是,我了解到java.util.concurrent 包不使用基于锁(睡眠/自旋)的机制来实现同步。

我的问题:

java并发包实现同步的机制是什么?因为自旋锁是 CPU 密集型的,而且由于频繁的上下文切换,睡眠锁比自旋锁更昂贵。

【问题讨论】:

  • 最好缩小问题范围。然而,作为一个开放者,Java 可以使用监视器、信号量、等待/通知、内存屏障、自旋锁和 cas。这些技术中的每一种都至少在 JDK 库中的一个地方使用。我知道 Java 尚未使用的主要技术是 simd,但我相信该领域正在开展工作。
  • @ChrisK SIMD 实际上用于 Arrays.fill 和类似操作的 JIT 编译器内部函数。
  • @ChrisK 这是我的信息来源:stackoverflow.com/a/21525039/1103872 似乎一些样板的for循环也被检测到并变成了内在的。
  • 信号量和 RecursiveLocks 和 ReadWriteLocks 锁。 .concurrent 包中的一些数据结构(例如,ConcurrentHashMap 和非阻塞队列)使用无锁算法,但任何可以阻塞直到其他线程执行必须正在使用的操作的方法调用某种锁。正如其他人已经指出的那样,“某种锁”可以通过 sun.misc.Unsafe 类使用本机调用来实现。

标签: java multithreading concurrency synchronization


【解决方案1】:

这在很大程度上取决于您使用 java.util.concurrent 包的哪些部分(在较小程度上取决于实现)。例如。从 Java 1.7 开始的 LinkedBlockingQueue 使用 ReentrantLocks 和条件,而例如java.util.concurrent.atomic 类或 CopyOnWrite* 类依赖于 volatiles + 本机方法(插入适当的内存屏障)。

锁、信号量等的实际本地实现也因架构和实现而异。

编辑:如果您真的关心性能,您应该measure 特定工作负载的性能。在 JVM 团队中有比我聪明得多的人,例如 A. Shipilev(其网站是有关此主题的大量信息),他们这样做并非常关心 JVM 性能。

【讨论】:

  • 我的问题是,java并发包中是否有任何类在不使用锁机制的情况下执行同步?
  • @overexchange - 你为什么要问这个?为什么重要?
  • @jtahlborn 这很重要,因为基于锁的同步需要更多的 cpu 周期,因为自旋锁需要更多的 cpu 周期,并且由于上下文切换,睡眠锁的成本是自旋锁的 1000 倍。
  • @overexchange - 您从哪里获得性能信息和对 Java 实现的理解?
  • @overexchange - 我的猜测是您正在参与一个过早优化的严重案例。相反,正确实现您的代码。然后进行性能测试。然后,如果您发现问题,请优化该问题(可能是锁定,也可能是其他问题)。任何形式的锁定或无锁数据结构都不是适合所有情况的最佳。您需要进行真正的测试以确定哪个最适合您的情况。
【解决方案2】:

查看source code for java.util.concurrent 可以最好地回答这个问题。精确的实现取决于您所指的类。

例如,许多实现都使用volatile 数据和sun.misc.Unsafe,例如延迟。与本机操作进行比较和交换。 Semaphore(通过AbstractQueuedSynchronizer)大量使用了这个。

您可以浏览那里的其他对象(使用该站点左侧的导航窗格)来查看其他同步对象以及它们是如何实现的。

【讨论】:

    【解决方案3】:

    简短的回答是否定的。

    与同步集合相比,并发集合不使用锁实现。

    我自己也遇到了与所问问题完全相同的问题,希望始终了解细节。最终帮助我完全理解幕后发生的事情是阅读Java并发实践中的以下章节:

    5.1 同步集合
    5.2 并发集合

    这个想法是基于做原子操作,基本上不需要锁,因为它们是原子的。

    【讨论】:

    • 很抱歉,当你说不时,我没有得到你。
    • 问题 ;) java 并发包是使用锁实现的吗?没有。
    • 我认为引用的文档部分没有解决我的问题。
    【解决方案4】:

    OP 的问题和评论交流似乎包含相当多的混乱。我将避免回答字面上的问题,而是尝试给出一个概述。


    为什么java.util.concurrent 成为今天的推荐做法?

    因为它鼓励良好的应用程序编码模式。潜在的性能提升(可能会实现也可能不会实现)是一种奖励,但即使没有性能提升,仍然建议使用java.util.concurrent,因为它可以帮助人们编写正确的代码。速度快但有缺陷的代码没有价值。

    java.util.concurrent 如何鼓励良好的编码模式?

    在很多方面。我只列举几个。

    (免责声明:我来自 C# 背景,对 Java 的并发包没有全面的了解;尽管 Java 和 C# 对应物之间存在很多相似之处。)

    并发数据收集简化了代码。

    • 通常,当我们需要从不同线程访问和修改数据结构时,我们会使用锁定。
    • 典型的操作包括:
      • 锁定(成功前被阻止),
      • 读取和写入值,
      • 解锁。
    • 并发数据收集通过将所有这些操作整合到一个函数调用中来简化此操作。结果是:
      • 调用方的代码更简单,
      • 可能更优化,因为库实现可能使用与 JVM 对象监视器不同(且更有效)的锁定或无锁机制。
      • 避免常见的竞争条件陷阱:Time of check to time of use

    两大类并发数据收集类

    并发数据收集类有两种风格。它们是为非常不同的应用需求而设计的。要从“良好的编码模式”中受益,您必须知道在每种情况下使用哪一种。

    • 非阻塞并发数据收集
      • 这些类可以保证在确定的时间内做出响应(从方法调用返回) - 无论操作成功还是失败。它永远不会死锁或永远等待。
    • 阻止并发数据收集
      • 这些类利用 JVM 和 OS 同步功能将数据操作与线程控制链接在一起。
      • 正如您所提到的,它们使用睡眠锁。如果对阻塞并发数据集合的阻塞操作没有立即满足,则请求该操作的线程进入睡眠状态,并在操作满足时被唤醒。

    还有一种混合方式:阻塞并发数据收集,允许进行快速(非阻塞)检查以查看操作是否会成功。这种快速检查可能会受到“检查时间到使用时间”竞争条件的影响,但如果使用正确,它可能对某些算法有用。

    java.util.concurrent 包可用之前,程序员通常不得不编写自己的穷人替代方案。很多时候,这些糟糕的替代品都有隐藏的错误。

    除了数据收集?

    CallableFutureExecutor 对于并发处理非常有用。可以说,这些模式提供了与命令式编程范式截然不同的东西。

    应用程序现在可以:

    • Callable 允许将“工作单元”与将要处理的数据打包在一起,
    • Future 为不同的工作单元提供了一种表达其顺序依赖关系的方法——哪个工作单元必须在另一个工作单元之前完成,等等。
      • 换句话说,如果两个不同的Callable 实例没有表明任何顺序依赖关系,那么如果机器能够并行执行,它们就有可能同时执行。
    • Executor 指定如何执行这些工作单元的策略(约束)和策略。

    据报道,原始java.util.concurrent 中缺少的一件大事是,当Future 提交到Executor 时,它能够在成功完成Future 后安排新的Callable。有提议要求ListenableFuture

    (在 C# 中,类似的工作单元可组合性称为 Task.WhenAllTask.WhenAny。它们一起可以表达许多众所周知的多线程执行模式,而无需显式创建和销毁线程用自己的代码。)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-11-08
      • 2016-10-15
      • 2013-11-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多