zoukankan      html  css  js  c++  java
  • AQS-volatile、CAS

    1,AQS

      java.util.concurrent包的核心类是AbstractQueuedSynchronizer,AQS是一个同步器+阻塞锁的基本架构,用于控制加锁和释放锁,并在内部维护一个FIFO的线程等待队列。

     

      AbstractOwnableSynchronizer是一个可以由线程以独占方式拥有的同步器,这个类为创建锁和维护同步器提供基础。虽然它本身不管理或使用此信息,但其子类和其它工具可以维护相应的值来诊断信息。

      AbstractQueuedSynchronizer用虚拟队列的方式来管理线程中锁的获取与释放,同时也提供了各种情况下的线程中断,这个类虽然提供了默认的同步实现,但是获取锁和释放锁的实现被定义为抽象方法,由子类实现。这样做的目的是使开发人员可以自定义锁的获取与释放。

      Sync是ReentrantLock的内部抽象类,实现了简单的锁的获取与释放。它有两个子类,NonfairSync和FairSync,分别表示非公平锁和公平锁,且都是ReentrantLock的内部类,ReentrantLock实现了Lock接口的lock-unlock方法,这个方法会根据fail参数决定使用NonfailSync还是FairSync。

      ReentrantReadWriteLock是Lock的另一种实现方式,ReentrantLock是一个排它锁,同一时间只允许一个线程访问,而ReentrantReadWriteLock允许多个线程同时访问,但不允许写线程和读线程、写线程和写线程同时访问。与排它锁相比,能够提供更高的并发性。

     

      AQS内部实现使用链表的结构。AQS维护了一个volatile int state和一个FIFO线程等待队列(使用双向链表实现,当多线程征用资源被阻塞时进入此队列),只有当Head节点持有的线程释放了资源后,下一个线程才能获得资源。在这个工作模型中,state即是AQS的同步状态标量,state表示线程争抢的资源,如果是0,那么表明没有线程正在等待资源,如果为n(n>0)那么说明有n个线程正在等待资源释放。在AQS中state有三种访问方式getState、setState、compareAndSetState。

    1 //AQS等待队列添加节点过程
    2 pred = tail
    3 node.prev = pred
    4 tail = node;
    5 pred.next = node

    2,Volatile

      在AQS队列中,大量成员变量使用了volatile修饰,比如队列的head和tail。这些变量在多线程环境下,极有可能被线程A使用的过程中,被线程B修改。在使用synchronized进行线程同步的时候,只能让A或B中一个线程先执行完,然后再让等待的线程继续执行,这种方式性能不佳。线程安全有三要素:可见性、有序性、原子性。线程安全是指在多线程情况下,对共享内存的使用,不会因为不同线程的访问和修改而发生不期望的结果。

    1 private volatile int state;
    2 private transient volatile Node head;//队列头
    3 private transient volatile Node tail;//队列尾

    1)      volatile用于解决多核CPU高速缓存导致的变量不同步问题(可见性问题)。volatile有三个作用:

    可见性是指当多个线程在访问同一个变量时,一个线程修改了变量的值,其它线程应该能立即看到修改后的值。可见性问题本质上是一个硬件问题,其根源在于:CPU的高速缓存的读取速度远远快于主存。CPU在读取一个变量的时候,会把数据先读到缓存,这样下次再访问同一个数的时候就直接从缓存中读了,显然提高了读取性能,而多核CPU有多个这样的缓存,当CPU1修改了某个变量,如果此时CPU2已经把这个变量读取到缓存,CPU2仍会去缓存中读取这个变量,但此时缓存中的变量和主存中的是不一样的。volatile的实现原理是内存屏障(MemoryBarrier),其原理为:当CPU写数据时,如果发现一个变量在其它CPU中存在副本,那么会发出信号量通知其它CPU将该副本对应的缓存行置为无效状态,其它CPU读取变量副本时会发现缓存行是无效的,然后它会从主存从新读取。

    2)      volatile可以解决指令重排的问题(有序性问题)

    有序性是指程序执行的顺序应当按照代码的先后顺序执行。一般情况下,程序是按照顺序执行的,但在运行期间,CPU会对指令进行优化,没有依赖关系的指令,它们的顺序可能被重排,在单线程下,发生重排是没有问题的,CPU保证了顺序不一定但结果一定一致。但在多线程环境下重排会引起很大问题。这对关键成员变量使用volatile来修饰,CPU就不会对它做重新排序优化。

    3)      volatile不保证操作的原子性

    原子性是指一个或多个操作,要么全部连续执行且不被任何因素中断,要么不执行。volatile不能保证操作的原子性。

    3,CAS

           volatile保证了线程安全的两个必要条件:可见性和有序性。只要再找出一种能够保证多线程环境下原子性的方式就能实现线程安全。Synchronized显然能保证原子性,但效率过低。AQS采用的是一种效率更高的操作方式,Compare And Swap,CAS作用是对指定内存地址的数据,校验它的值是否为期望值,如果是,修改为新值,返回值表示是否修改成功。

           在通常的开发流程中,Java代码被编译为class字节码,字节码在JVM中解释执行,最终会作为一个一个的指令集由CPU处理,同一句java语句可能产生多条指令集,由于CPU的优化策略和多线程同时执行,指令集可能不会顺序执行,这样就产生了操作原子性问题。

      CAS和代码级的比较交换相比,其特殊之处在于,它能保证一个序列的指令必定会连续执行,不被其它指令妨碍。

      原子操作问题发生在CPU指令顺序问题上,java代码只是部分可以控制到这个级别。虽然java不建议操作底层,但是java还是提供了一个非公开的类,sun.misc.Unsafe,专门用来进行较为底层的操作,它提供的方法都是native本地方法,它封装了一些列原子化操作。

      native方法没有方法体,不需要java代码实现,它的真正方法体来自于底层动态链接库(dll/so),有c/c++实现。

      sun.misc.Unsafe中有许多类似compareAndSwapInt(Object,offset,expect,newValue)的方法,此方法用于比较Object对象在偏移量offset位置的数值是否为期望值expect,如果是,修改为newValue,返回true。最终达到了intVal++节点级的原子操作。CAS采用的是自旋锁,与synchronized相比,充分利用了资源,效率更高。

      AQS使用volatile保证了head、tail节点操作中的有序性和可见性,又使用Unsafe/CAS保证了节点操作的原子性。可以认为其相关操作是线程安全的。

  • 相关阅读:
    字符串匹配之朴素匹配
    XSS的攻击原理
    使用metasploit收集邮箱
    C++实现折半插入排序
    C++插入排序实现
    Java中的NIO
    Hashtable和HashMap区别(面试)
    面向对象:封装(一):构造函数;类的主方法;权限修饰符;对象的创建
    switch多分支语句
    递归和字母数字生成随机数
  • 原文地址:https://www.cnblogs.com/guanghe/p/13457984.html
Copyright © 2011-2022 走看看