`
san_yun
  • 浏览: 2653176 次
  • 来自: 杭州
文章分类
社区版块
存档分类
最新评论

volatile, 用更低的代价替代同步

    博客分类:
  • java
 
阅读更多

为什么 使用volatile比同步代价更低?
同步的代价, 主要由其覆盖范围决定, 如果可以降低同步的覆盖范围, 则可以大幅提升程序性能.

而volatile的覆盖范围仅仅变量级别的. 因此它的同步代价很低.

volatile原理是什么?
volatile的语义, 其实是告诉处理器, 不要将我放入工作内存, 请直接在主存操作我.(工作内存详见java内存模型)

因此, 当多核或多线程在访问该变量时, 都将直接 操作 主存, 这从本质上, 做到了变量共享.

volatile的有什么优势?
1, 更大的程序吞吐量
2, 更少的代码实现多线程
3, 程序的伸缩性较好
4, 比较好理解, 无需太高的学习成本

volatile有什么劣势?
1, 容易出问题
2, 比较难设计

volatile运算存在脏数据问题

volatile仅仅能保证变量可见性, 无法保证原子性.

volatile的race condition示例:

public
 class
 TestRaceCondition {

    private
 volatile
 int
 i =
 0
;

 
    public
 void
 increase(
)
 {

       i++;

    }

 
    public
 int
 getValue(
)
 {

       return
 i;

    }

}

当多线程执行increase方法时, 是否能保证它的值会是线性递增的呢?
答案是否定的.

原因:
这里的increase方法, 执行的操作是i++, 即 i = i + 1;
针对i = i + 1, 在多线程中的运算, 本身需要改变i的值.
如果, 在i已从内存中取到最新值, 但未与1进行运算, 此时其他线程已数次将运算结果赋值给i.
则当前线程结束时, 之前的数次运算结果都将被覆盖.

即, 执行100次increase, 可能结果是 < 100.
一般来说, 这种情况需要较高的压力与并发情况下, 才会出现.

如何避免这种情况?
解决以上问题的方法:
一种是 操作时, 加上同步.
这种方法, 无疑将大大降低程序性能, 且违背了volatile的初衷.

第二种方式是, 使用硬件原语(CAS), 实现非阻塞算法
从CPU原语上, 支持变量级别的低开销同步.

CPU原语-比较并交换(CompareAndSet),实现非阻塞算法

什么是CAS?
cas是现代CPU提供给并发程序使用的原语操作. 不同的CPU有不同的使用规范.

在 Intel 处理器中,比较并交换通过指令的 cmpxchg 系列实现。
PowerPC 处理器有一对名为“加载并保留”和“条件存储”的指令,它们实现相同的目地;
MIPS 与 PowerPC 处理器相似,除了第一个指令称为“加载链接”。

CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)

什么是非阻塞算法?
一个线程的失败或挂起不应该影响其他线程的失败或挂起.这类算法称之为非阻塞(nonblocking)算法

对比阻塞算法:
如果有一类并发操作, 其中一个线程优先得到对象监视器的锁, 当其他线程到达同步边界时, 就会被阻塞.
直到前一个线程释放掉锁后, 才可以继续竞争对象锁.(当然,这里的竞争也可是公平的, 按先来后到的次序)

CAS 原理:

我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。

CAS使用示例(jdk 1.5 并发包 AtomicInteger类分析:)

/**
    * Atomically sets to the given value and returns the old value.
    *
    * @param newValue the new value
    * @return the previous value
    */

   public
 final
 int
 getAndSet(
int
 newValue)
 {

       for
 (
;;
)
 {

           int
 current =
 get(
)
;

           if
 (
compareAndSet(
current, newValue)
)

               return
 current;

       }

   }

 
   public
 final
 boolean
 compareAndSet(
int
 expect, int
 update)
 {

       return
 unsafe.compareAndSwapInt
(
this
, valueOffset, expect, update)
;

   }

这个方法是, AtomicInteger类的常用方法, 作用是, 将变量设置为指定值, 并返回设置前的值.
它利用了cpu原语compareAndSet来保障值的唯一性.

另, AtomicInteger类中, 其他的实用方法, 也是基于同样的实现方式.
比如 getAndIncrement, getAndDecrement, getAndAdd等等.

CAS语义上存在的 ” ABA 问题”

什么是ABA问题?
假设, 第一次读取V地址的A值, 然后通过CAS来判断V地址的值是否仍旧为A, 如果是, 就将B的值写入V地址,覆盖A值.

但是, 语义上, 有一个漏洞, 当第一次读取V的A值, 此时, 内存V的值变为B值, 然后在未执行CAS前, 又变回了A值.
此时, CAS再执行时, 会判断其正确的, 并进行赋值.

这种判断值的方式来断定内存是否被修改过, 针对某些问题, 是不适用的.

为了解决这种问题, jdk 1.5并发包提供了AtomicStampedReference(有标记的原子引用)类, 通过控制变量值的版本来保证CAS正确性.

其实, 大部分通过值的变化来CAS, 已经够用了.

jdk1.5原子包介绍(基于volatile)

包的特色:
1, 普通原子数值类型AtomicInteger, AtomicLong提供一些原子操作的加减运算.

2, 使用了解决脏数据问题的经典模式-”比对后设定”, 即 查看主存中数据是否与预期提供的值一致,如果一致,才更新.

3, 使用AtomicReference可以实现对所有对象的原子引用及赋值.包括Double与Float,
但不包括对其的计算.浮点的计算,只能依靠同步关键字或Lock接口来实现了.

4, 对数组元素里的对象,符合以上特点的, 也可采用原子操作.包里提供了一些数组原子操作类
AtomicIntegerArray, AtomicLongArray等等.

5, 大幅度提升系统吞吐量及性能.

具体使用, 详解java doc.

分享到:
评论

相关推荐

    Volatile详解,深入学习Volatile

    《深入理解Volatile》 Volatile是C/C++编程语言中的一个关键字,它与const一起被称为"cv特性",用于指示变量的值可能被系统...但在使用时,需要注意其局限性,特别是在多线程环境下,volatile并不能完全替代同步机制。

    Java线程:volatile关键字

    volatile 变量的同步性较差,但它有时更简单并且开销更低。volatile 变量可以被看作是一种 “程度较轻的 synchronized”;与 synchronized 块相比,volatile 变量所需的编码较少,并且运行时开销也较少,但是它所能...

    volatile的使用方法

    volatile 关键字在 C 语言中的使用方法 在 C 语言中,volatile 关键字是用来指定变量的存储类别的,它告诉编译器这个变量的值可能会在编译器不知道的情况下被修改,因此需要重新加载这个变量的值。volatile 关键字...

    volatile关键字使用

    在编写多线程程序时,如果需要更高级别的同步保障,还需要使用锁、原子变量(如C++11中的`std::atomic`)或其他并发原语。 此外,`volatile`关键字的使用需要谨慎,过度使用可能会导致性能下降,因为它阻止了编译器...

    volatile源码分析1

    在深入理解volatile时,我们需要考虑JVM内存模型、处理器缓存以及底层的汇编指令,这有助于我们更好地理解和使用volatile,解决并发编程中的问题。在实际开发中,合理利用volatile可以避免不必要的锁同步,提升程序...

    volatile使用详解

    - 示例:如果多个任务需要通过共享变量来协调它们的行为,那么这些变量应该使用 `volatile` 来标记,以确保线程间能够正确地同步状态。 3. **硬件寄存器访问**:存储器映射的硬件寄存器通常也需要用 `volatile` ...

    C#中volatile与lock用法

    volatile是C#中用于控制同步的关键字,其意义是针对程序中一些敏感数据,不允许多线程同时访问,保证数据在任何访问时刻,最多有一个线程访问,以保证数据的完整性,volatile是修饰变量的修饰符。 1、volatile的使用...

    stm32 volatile变量的正确使用

    本文将深入探讨`volatile`关键字的基本概念、使用场景以及注意事项,并结合实际示例来帮助读者更好地理解和应用这一概念。 #### `volatile`关键字详解 在C语言中,`volatile`是一个类型限定符,用于告知编译器不要...

    volatile的用法讲解

    1. 并行设备的硬件寄存器(如:状态寄存器):这些寄存器的值可能会被外部设备改变,因此需要使用volatile修饰符来确保编译器每次都读取最新的值。 2. 一个中断服务子程序中会访问到的非自动变量(Non-automatic ...

    volatile用法

    `volatile`和`synchronized`都是Java中的关键字,虽然它们都可以用于处理多线程同步问题,但作用机制不同。`volatile`确保了变量的修改对于其他线程是可见的,而`synchronized`则提供了互斥访问,保证同一时间只有...

    volatile的使用

    一般说来,volatile用在如下的几个地方: 1、中断服务程序中修改的供其它程序检测的变量需要加volatile; 2、多任务环境下各任务间共享的标志应该加volatile; 3、存储器映射的硬件寄存器通常也要加volatile说明,...

    volatile 变量的说明

    总结来说,volatile是处理多线程并发和设备驱动编程时的重要工具,它确保了变量的实时性和可见性,但不能替代锁或其他同步机制来处理复杂的并发问题。理解和正确使用volatile是编写高效、可靠的并发程序的关键。

    单片机C语言中volatile的作用

    使用`volatile`关键字可以帮助避免一些基本的同步问题,但需要注意的是,它并不能完全解决所有并发问题,还需要结合其他同步手段一起使用。 #### 面试中的`volatile`问题 接下来,我们将通过几个具体的面试问题来...

    volatile的使用1

    【volatile关键字】在Java编程语言中扮演着至关重要的角色,主要负责解决并发环境下的数据同步问题。volatile确保了变量在多线程环境下的...理解并正确使用volatile,可以帮助开发者编写出更高效、更安全的多线程代码。

    C语言中关于关键字volatile的用法

    尽管如此,在使用volatile时也要注意,它并不是万能的,比如它并不能替代锁机制来保证多线程间的同步。对于复杂的同步问题,还需要结合其他多线程编程技术来解决。 最后,回到示例代码中的问题,使用volatile变量时...

    const和volatile分析

    2. 多线程编程:在多线程环境下,当一个变量可能被其他线程修改时,应使用`volatile`,让编译器知道这个变量的值可能会在不通过程序控制的情况下发生变化。 3. 实时系统:在实时系统中,`volatile`用于声明受外部...

    volatile详解

    ### volatile详解 #### 一、volatile的基本概念与作用 `volatile`关键字在编程语言中扮演着重要的...通过理解`volatile`的基本概念、工作原理以及应用场景,开发者可以更有效地利用这一特性来编写健壮、可靠的程序。

    volatile的作用

    需要注意的是,虽然`volatile`可以防止编译器的某些优化,但它并不能完全替代锁机制或其他同步手段。在并发编程中,为了实现更复杂的同步逻辑,还需要结合使用其他工具和技术。 ### 总结 `volatile`关键字主要用于...

    volatile的用法

    10. volatile在实际编程中的注意事项:尽管volatile可以保证变量在多线程或中断环境下的正确访问,但过多地使用volatile也可能会导致程序效率下降。因此,在使用volatile时应谨慎,并且评估其对程序性能的可能影响。...

Global site tag (gtag.js) - Google Analytics