是使用锁实现java并发包吗?

时间:2014-10-24 12:00:53

标签: java multithreading concurrency synchronization

从概念上讲,

Mutex

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

Semaphore

Condition Variable

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

前两个使用自旋锁实现(忙碌等待)。

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

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

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

我的问题:

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

4 个答案:

答案 0 :(得分:3)

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

Locks,Semaphores等的实际原生实现也因架构和实现而异。

编辑:如果您真的关心性能,则应measure执行特定工作负载。在JVM团队中,有些人比我更聪明,比如A. Shipilev(他们的网站是关于这个主题的大量信息),他们这样做并且非常关心JVM的性能。

答案 1 :(得分:2)

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

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

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

答案 2 :(得分:0)

简短的回答是否定的。

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

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

5.1同步集合
5.2并发收藏

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

答案 3 :(得分:0)

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中遗漏了一件大事,就是当Callable成功完成Future提交给Executor时,能够安排新ListenableFuture }}。有些提案要求Task.WhenAny

(在C#中,类似的工作单元可组合性称为Task.WhenAll和{{1}}。它们共同表达了许多众所周知的多线程执行模式,而无需明确使用自己的代码创建和销毁线程。)