`

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

阅读更多

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

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

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

 

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

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

 

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

 

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

 

volatile运算存在脏数据问题

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

 

volatile的race condition示例:

Java代码 复制代码 收藏代码
  1. public class TestRaceCondition {   
  2.     private volatile int i = 0;   
  3.   
  4.     public void increase() {   
  5.        i++;   
  6.     }   
  7.   
  8.     public int getValue() {   
  9.        return i;   
  10.     }   
  11. }  
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类分析:)

 

Java代码 复制代码 收藏代码
  1. public final int getAndSet(int newValue) {   
  2.      for (;;) {   
  3.          int current = get();   
  4.          if (compareAndSet(current, newValue))   
  5.              return current;   
  6.      }   
  7.  }   
  8.   
  9.  public final boolean compareAndSet(int expect, int update) {   
  10.      return unsafe.compareAndSwapInt(this, valueOffset, expect, update);   
  11.  }  
  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.

分享到:
评论

相关推荐

    笔记-9、JMM和底层实现原理1

    在并发编程领域,理解和掌握Java内存模型(JMM)及其底层实现原理至关重要,因为这些问题直接影响到多线程程序的正确性和性能。以下是对这些关键概念的详细解释。 1. 并发编程领域的关键问题 并发编程是现代软件...

    Java并发编程与高并发解决方案笔记-基础篇.docx

    在多核CPU中,每个线程可以分配到一个独立的处理器核上,真正实现并行执行。 - **高并发**:高并发是指系统能够同时处理大量请求,提升服务的吞吐量。为了实现高并发,我们需要关注线程安全和资源的高效利用。 2. ...

    笔记14-JAVAEE之多线程初阶

    - **为何需要线程**:随着多核CPU的发展,通过并发编程能更好地利用硬件资源。线程相比进程更轻量级,创建、销毁和调度更快,适合处理等待IO等场景。 - **进程与线程的区别**:进程是包含线程的实体,每个进程至少...

    Java并发编程与高并发解决方案-学习笔记-www.itmuch.com.pdf

    本文将基于文档《Java并发编程与高并发解决方案-学习笔记***.pdf》中提供的内容,来详细阐述并发编程和高并发的基本概念、CPU多级缓存与缓存一致性、以及Java内存模型。 ### 并发与高并发概念 在现代多线程编程中...

    狂神说多线程详解.rar

    综上所述,《狂神说多线程详解》涵盖了多线程编程的基础理论、实践技巧以及常见的问题和解决方案,对于开发者来说是一份宝贵的参考资料。通过学习,开发者可以深入理解多线程的工作原理,并在实际项目中有效应用。

    线程学习实例和笔记

    在IT行业中,线程是操作系统资源分配的基本单位,也是多任务并行处理的关键...通过实践和学习这些实例,你可以更好地掌握线程编程的技巧,并在未来的工作中更有效地利用多核处理器的优势,编写出高效、稳定的并发程序。

    尚硅谷JUC视频笔记整理,很详细和全面,帮你迅速掌握JUC

    本笔记整理涉及了JUC的内存可见性、volatile关键字以及CAS算法和原子变量等多个方面,以下是对这些知识点的详细说明。 ### 内存可见性 在多线程环境下,内存可见性是指当一个线程修改了某个共享变量的值,其他线程...

    java并发笔记

    ### Java并发知识点详解 #### 一、JDK发展与并发改进 - **JDK1.0**: 提供了首个纯解释执行的Java虚拟机(JVM)。此时并未涉及并发技术。...理解并发的基本原理和技巧对于开发高效可靠的软件系统至关重要。

    并发编程笔记1.zip

    在本笔记中,我们将深入探讨并发编程的基础概念、核心原理以及在实践中遇到的问题与解决方案。 1. **线程与进程** - **进程**:是操作系统资源分配的基本单位,每个进程都有独立的内存空间。 - **线程**:是执行...

    Java线程系列操作.zip

    线程在多核处理器或单核但支持并发操作的系统中尤为重要,可以提高程序的执行效率和响应速度。在这个"Java线程系列操作"的资料中,我们可以看到三个示例文件:ThreadPoolDemo.java、SyncDemo2.java和ThreadDemo2....

    Java Concurrency In Practice Learning Note

    《Java并发编程实践》学习笔记 Java并发编程是Java开发者必须掌握的重要技能,尤其是在多核处理器和高并发应用中。本书《Java Concurrency In Practice》是Java并发编程的经典之作,由Brian Goetz、Tim Peierls、...

    狂神说笔记,个人觉得不赖

    本笔记“狂神说笔记”主要针对Java的学习进行了详尽的讲解,尤其是对于JavaSE(Java Standard Edition)部分的深入剖析,旨在帮助开发者掌握Java的基础知识和进阶技巧。 多线程是JavaSE中的核心特性之一,它允许多...

    并发编程之一 日常学习笔记

    综上所述,这一系列学习笔记涵盖了并发编程的关键概念和实战技巧,包括Java内存模型、线程池、并发容器的使用以及常见数据结构的线程安全问题。通过深入学习这些内容,开发者可以更好地理解和解决多线程环境下的编程...

    Java并发编程与高并发解决方案-学习笔记.pdf

    Java内存模型(JMM)也在笔记中被提及,它规定了Java虚拟机与计算机内存如何协同工作,包括线程如何以及何时能够看到其他线程修改过的共享变量的值,以及如何同步地访问这些共享变量。Java内存模型是并发编程中一个...

    Java并发编程学习笔记.rar

    1. **线程与进程**:在多任务环境中,线程是执行的基本单元,而进程是资源分配的最小单位。Java中的线程可以通过继承`Thread`类或实现`Runnable`接口来创建。 2. **线程同步**:为了解决多个线程间的竞态条件,Java...

    Java并发编程学习笔记

    Java并发编程是指在Java语言中编写多线程和多任务执行的程序,以便更高效地利用计算机的多核处理器资源。并发编程是Java高级编程技能中的重要组成部分,尤其是在需要处理大量数据、提供快速响应、实现高吞吐量和高可...

    java并发笔记,包含线程池

    以上只是Java并发编程的一些基础知识点,实际的Java并发笔记可能还会深入探讨更多高级话题,如原子操作类`Atomic*`、并发容器的设计原理、线程安全的类实现方式等。通过深入学习和实践,开发者可以更好地驾驭Java...

    java并发学习笔记

    - **并行与并发**:并行是指多个任务在同一时刻真正同时执行,通常需要多核CPU支持。并发则是指多个任务在宏观上看起来同时执行,实际上是快速切换执行。 2. **启动线程的三种方式** - **继承Thread类**:创建新...

Global site tag (gtag.js) - Google Analytics