首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > 编程 >

多核线程札记-volatile原理与技巧

2012-10-24 
多核线程笔记-volatile原理与技巧volatile, 用更低的代价替代同步为什么 使用volatile比同步代价更低??同

多核线程笔记-volatile原理与技巧

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

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

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

?

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

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

?

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

?

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

?

volatile运算存在脏数据问题

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

?

volatile的race condition示例:

?多核线程札记-volatile原理与技巧多核线程札记-volatile原理与技巧
    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类分析:)

    ?

    ?多核线程札记-volatile原理与技巧多核线程札记-volatile原理与技巧
      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.

热点排行