【问题标题】:Mutual exclusion Vs Atomic variable互斥 Vs 原子变量
【发布时间】:2017-12-02 10:48:15
【问题描述】:

原子操作 - 一次有效地发生或根本不发生的动作 例如:java.util.concurrent.atomic.AtomicInteger

互斥 - 防止同时访问共享资源 例如:synchronized


使用互斥方法,SynchronizedCounter 是线程安全的,

class SynchronizedCounter {
    private int c = 0;

    public synchronized void increment() {
        c++;
    }

    public synchronized void decrement() {
        c--;
    }

    public synchronized int value() {
        return c;
    }

}

使用原子变量方法,AtomicCounter 是线程安全的,

import java.util.concurrent.atomic.AtomicInteger;

class AtomicCounter {
    private AtomicInteger c = new AtomicInteger(0);

    public void increment() {
        c.incrementAndGet();
    }

    public void decrement() {
        c.decrementAndGet();
    }

    public int value() {
        return c.get();
    }
}

1) 在上面的代码中,为什么原子变量方法优于互斥方法?

2) 总的来说,互斥原子变量方法的目标不一样吗?

【问题讨论】:

  • 请在问题中提供完整的上下文,无需点击链接。
  • @hyde 查询已编辑
  • 它“更好”是因为它使用了比同步更快的低级指令,而且使用它比使用同步更难引入错误。
  • @JBNizet 原子变量方法是否提供无锁同步?
  • 如果你的目标是只增加一个整数,原子地,是的。对于更复杂的用例(例如以原子方式修改两个引用),否。

标签: java multithreading atomic mutual-exclusion


【解决方案1】:

在您的示例中,两个类都提供了“功能上”等效的结果,主要在性能上有所不同。如果您只需要一个简单的计数器,那么原子更合适,因为互斥通常会更昂贵。原因是原子操作由单个 CPU 指令执行,其中互斥需要更昂贵的高级操作,通常由操作系统处理。

相互排除允许跨多个变量协调更改。为了扩展您的示例,想象一个更新两个(或更多)计数器的系统。计数器初始化如下;

  • a = 0
  • b = 1

在下表中,每一行代表将导致所需状态的事务。每一列都是时间的流逝(例如 CPU 周期)。

本系统的正确性定义如下;

  1. 允许过时的读取(例如,之前的整个事务)。
  2. 部分读取无效(例如,两个或多个事务的混合视图)。

粗黑线表示可以读取值的同步时间点。使用 Atomics 可以按所展示的顺序执行,这是不可取的。互斥通过阻止或提供陈旧的读取来权衡性能与正确性。

为了阐明为什么“正确性”很重要,假设“a”是净收入,“b”是总收入。通常更愿意报告过去的事情或说“一瞬间”,而不是提供不相加的值。

【讨论】:

【解决方案2】:

不同之处在于第一个使用同步的实现是阻塞的,而第二个不是。 《多处理器编程艺术》一书的前三章全面介绍了这两种方法的差异和后果。

这是第 3.7 章中的一些陈述

无等待和无锁的非阻塞进度条件保证 计算作为一个整体取得进展,独立于如何 系统调度线程。

阻塞实现的进展条件:无死锁和 无饥饿属性。这些属性是依赖进度 条件:只有当底层平台(即 操作系统)提供了一定的保证。原则上, 无死锁和无饥饿属性在以下情况下很有用 操作系统保证每个线程最终离开每个 临界区。在实践中,这些属性是有用的,当 操作系统保证每个线程最终离开每个 关键部分及时。方法依赖的类 基于锁的同步最多可以保证依赖进度 特性。这个观察是否意味着基于锁的算法 应该避免?不必要。如果在一个中间抢占 临界区足够少,然后依赖阻塞 进度条件与他们的实际情况无法区分 非阻塞对应物。如果抢占足够普遍导致 担心,或者如果基于抢占式延迟的成本足够 高,那么考虑非阻塞进度条件是明智的。

为并发对象实现选择一个进度条件 取决于应用程序的需要和特性 的底层平台。绝对的无等待和无锁 进度属性具有良好的理论属性,它们适用于 几乎任何平台,它们提供有用的实时保证 应用,如音乐、电子游戏和其他互动 应用程序。依赖无阻塞、无死锁和 无饥饿的财产依赖于提供的保证 底层平台。然而,鉴于这些保证,受抚养人 属性通常允许更简单和更有效的实现。

Java 中相同逻辑的非阻塞和阻塞实现的好例子是ConcurrentLinkedQueueLinkedBlockingQueue。虽然LinkedBlockingQueue 由于非阻塞属性而看起来更有吸引力,但有时在排队/出列等待新元素时被阻塞并为其他线程提供调度时间而不是立即获得空结果(null 或异常)并旋转会更有用当前线程的繁忙循环。

对于计数器,选择非阻塞方法肯定更有意义,因为硬件 CAS 操作支持也更快。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-06-23
    • 1970-01-01
    • 1970-01-01
    • 2021-01-24
    • 2015-12-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多