zoukankan      html  css  js  c++  java
  • 多线程编程核心技术(十六)原子类

    并发编程的几个注意点,原子性一直是需要提到的。

    在思想里面,一个是volatile实现可见性,或者加锁实现原子性。syn的本质其实是管程(也就是监视器),JVM层面的。Lock是JDK层面的,AQS实现

    还有一个就是原子性。Java 里面提供的是Atomic家族。

    原子性!=CAS,CAS是一种思想,一种乐观的思想。CAS=CompareAndSwaping。比较并且交换。

    CAS伪代码:

    首先计算 newValue = count+1,如果 cas(count,newValue) 返回的值不等于 count,则意味着线程在执行完代码①处之后,执行代码②处之前,count 的值被其他线程更新过。那此时该怎么处理呢?可以采用自旋方案,就像下面代码中展示的,可以重新读 count 最新的值来计算 newValue 并尝试再次更新,直到成功。

    class SimulatedCAS{
      volatile int count;
      // 实现count+=1
      addOne(){
        do {
          newValue = count+1; //①
        }while(count !=
          cas(count,newValue) //②
      }
      // 模拟实现CAS,仅用来帮助理解
      synchronized int cas(
        int expect, int newValue){
        // 读目前count的值
        int curValue = count;
        // 比较目前count值是否==期望值
        if(curValue == expect){
          // 如果是,则更新count的值
          count= newValue;
        }
        // 返回写入前的值
        return curValue;
      }
    }

     一般CAS都自带一个自旋的循环尝试。其实也就是使用多次访问来保证这段时间内,数据是干净的,而且不会影响别的程序,对此造成的重写覆盖。无锁方案相对互斥锁方案,最大的好处就是性能。互斥锁方案为了保证互斥性,需要执行加锁、解锁操作,而加锁、解锁操作本身就消耗性能;同时拿不到锁的线程还会进入阻塞状态,进而触发线程切换,线程切换对性能的消耗也很大。

    上面的伪代码是用了syn来提供原子性。但实际的Atomic里面使用的是硬件级的思想。unsafe来对JVM中C方法,C方法对操作系统,操作系统来对硬件资源。原因是本身CPU就是支持CAS指令的。

    Atomic类中的基本数据类型:Integer,Long,Boolean的主要方法

    getAndIncrement() //原子化i++
    getAndDecrement() //原子化的i--
    incrementAndGet() //原子化的++i
    decrementAndGet() //原子化的--i
    //当前值+=delta,返回+=前的值
    getAndAdd(delta) 
    //当前值+=delta,返回+=后的值
    addAndGet(delta)
    //CAS操作,返回是否成功
    compareAndSet(expect, update)
    //以下四个方法
    //新值可以通过传入func函数来计算
    getAndUpdate(func)
    updateAndGet(func)
    getAndAccumulate(x,func)
    accumulateAndGet(x,func)
    

     其实本质还是Unsafe中的C方法

     在Unsafe中的C底层的代码逻辑。也就是说其实还是需要用到强制刷新CPU的高级缓存,不过这个也是意料之内的,不然无法解决可见性问题。

    #include <sun/misc/Unsafe.h>

    static inline bool compareAndSwap (volatile jint *addr, jint old, jint new_val) { jboolean result = false; spinlock lock; // result=原先指针指向的地址的值(*addr)是否与旧的值(old)相等 if ((result = (*addr == old))) // 如果相等则把内存修改为新值 *addr = new_val; return result; }

      

    原子化的对象引用类型

    相关实现有 AtomicReference、AtomicStampedReference AtomicMarkableReference,利用它们可以实现对象引用的原子化更新。

    CAS其实会有个ABA问题,就是假设 count 原本是 A,线程 T1 在执行完代码①处之后,执行代码②处之前,有可能 count 被线程 T2 更新成了 B,之后又被 T3 更新回了 A,这样线程 T1 虽然看到的一直是 A,但是其实已经被其他线程更新过了。

    这边利用时间的天然重复性低的特性或者哨兵可以完成。也就是加一个版本号。每次执行 CAS 操作,附加再更新一个版本号,只要保证版本号是递增的,那么即便 A 变成 B 之后再变回 A,版本号也不会变回来(版本号递增的)。AtomicStampedReference 实现的 CAS 方法就增加了版本号参数,方法签名如下:

    boolean compareAndSet(
      V expectedReference,
      V newReference,
      int expectedStamp,
      int newStamp) 
    

      AtomicMarkableReference的区别之一就是它不会返回版本号,而是返回布尔值。

    无锁方案相对于互斥锁方案,优点非常多,首先性能好,其次是基本不会出现死锁问题(但可能出现饥饿和活锁问题,因为自旋会反复重试)。Java 提供的原子类大部分都实现了 compareAndSet() 方法,基于 compareAndSet() 方法

    参考文章:深入理解CAS算法原理

    smartcat.994
  • 相关阅读:
    关于OPC自动化接口编程(OPCDAAuto.dll)几点注意问题
    OPCDAAuto.dll的C#使用方法浅析(转载)
    微软系统工具包Sysinternals Suite官方下载地址
    C#的dll被其他程序调用时,获取此dll正确的物理路径
    根据存储过程,查询此过程的参数和参数数据类型讯息
    sql server中类似oracle中decode功能的函数
    c# HttpWebResponse 调用WebApi
    MariaDB10.4以上版本安装
    Windows server 2012 显示“我的电脑”
    Debian 9 启动后进入命令行
  • 原文地址:https://www.cnblogs.com/SmartCat994/p/14228325.html
Copyright © 2011-2022 走看看