相互排斥Vs原子变量

时间:2017-12-02 10:48:16

标签: java multithreading atomic mutual-exclusion

原子操作 - 一次完全有效或根本不有效的操作Ex: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)一般来说,互斥&的目标是什么? 原子变量方法,不一样吗?

2 个答案:

答案 0 :(得分:2)

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

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

  • a = 0
  • b = 1

在下表中,每一行代表一个将产生所需状态的交易。每列都是时间的推移(例如CPU周期)。

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

  1. 允许过时读取(例如,完整的先前交易)。
  2. 部分阅读无效(例如,两个或多个交易的混合视图)。
  3. enter image description here

    粗黑线表示可以读取值的同步时间点。使用Atomics,可以按所示的顺序执行,这是不合需要的。相互排除通过阻止或提供陈旧的阅读来换取正确性的表现。

    澄清为什么"正确性"是重要的想象" a"是净收入和" b"是总收入。通常优先报告过去的事情或者说" 1时刻"而不是提供不加起来的值。

答案 1 :(得分:1)

不同之处在于,第一个使用synchronized的实现是阻塞而第二个实现不是。 “多处理器编程艺术”一书的前三章介绍了这两种方法的差异和后果的综合描述。

以下是第3.7章的一些陈述

  

无等待和无锁无阻塞进度条件保证   计算作为一个整体取得进展,与方式无关   系统安排线程。

     

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

     

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

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

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