zoukankan      html  css  js  c++  java
  • 并发学习第六篇——Unsafe类和CAS

    之前看JUC的源码,一直有发现一个怪异的类:Unsafe,怪,是因为名字有点怪......它在sun.misc包下,

    不属于Java标准,但是很多java的高性能的类都基于Unsafe,而且它竟然可以直接操作内存,让java直

    接操作内存是很危险的,这个类就明着告诉我们,不安全,不要直接用我

    先看一个最常见到的方法:getUnsafe()

    private Unsafe() {} 
    
    private static final Unsafe theUnsafe = new Unsafe();
    
    @CallerSensitive  ------这个注解查了下是用来控制权限的,跟踪到最初的调用者,望文生义:调用者敏感的
    public static Unsafe getUnsafe() {
         Class<?> caller = Reflection.getCallerClass();
    //仅在BootstrapClassLoader加载时是合法的
    if (!VM.isSystemDomainLoader(caller.getClassLoader())) throw new SecurityException("Unsafe"); return theUnsafe; }

    很容易看出来是个单列模式,上面的注解,用来控制权限,可以跟踪到最初的调用者,可以望文生义的理解:

    调用者敏感;普通调用者调用直接抛异常,必须是systemDomainLoader这样的classLoader(BootstrapClassLoader)

    Unsafe类提供的API的脑图:

    CAS操作

    /** 
    * @param o 包含要修改field的对象
    * @param offset 对象中某field的偏移量
    * @param expected 期望值(预期的原值)
    * @param update 更新值
    * @return true | false
    */

    public
    native boolean compareAndSwapInt(Object obj, long offset, int expect, int update); public native boolean compareAndSwapLong(Object obj, long offset, long expect, long update); public native boolean compareAndSwapObject(Object obj, long offset, Object expect, Object update);

    Unsafe提供的CAS操作有这三个,都是compareAndSwapXXX的形式,执行的是一条CPU的原子指令(cmpxchg)

    CAS操作有3个操作数,内存值M,预期值E,新值U,如果M==E,则将内存值修改为U,否则啥都不做

    这个对比下mysql中的MVCC机制很好理解,MVCC存的是个隐藏的版本号值,做记录变更的操作时会拿

    先前已经获取到的版本号跟当前记录最新的版本号做比较(就像比较M和E),然后决定操作是成功还是失败

    典型应用:java.util.concurrent.atomic相关类、Java AQS、CurrentHashMap

    这里讲CAS,需要顺带提一下坊间关于CAS设计存在的三个问题:

    1、ABA问题

    很好理解,既然比较的是值,那最新的值A可能是变成了B后又变回了A,如果要求A不变指的是完全没变,而不是结果没变

    那CAS无法判断,这个时候可以加版本号解决,如上面说的mysql的mvcc的设计,java里有专门的针对此问题的设计类:

    AtomicStampedReference

    2、自旋消耗

    经常见到的是 while(!compareAndSwap(a,b,c)){},然后就一直自旋转,如果自旋一直不成功,会一直让CPU花费开销

    聪明的设计者自然有应对之策-------->  自适应自旋锁:

    即自旋的次数不再固定,而是由前一次在同一个锁上的自旋次数及锁的拥有者的状态来决定

    补充一下:LongAdder这个类,可以将单一变量的CAS操作分散为对数组Cell中多个cell的CAS操作,最后再求和,也是

    解决的一个思路,java8已提供

    3、只能支持单个共享变量的原子操作

    这个问题思路也好解决,多个共享变量组合成一个对象,只要保证对象可原子操作即可------->JUC中的AtomicReference

    内存操作

    包含堆外内存的分配、拷贝、释放、给定地址值操作等方法

    在Java中创建的对象都处于堆内内存(heap)中,堆内内存是由JVM所管控的Java进程内存,遵循JVM的内存

    管理机制,JVM会采用垃圾回收机制统一管理堆内存。

    堆外内存存在于JVM管控之外的内存区域,Java中对堆外内存的操作,依赖于Unsafe提供的和memory相关的

    native方法,如分配内存allocateMemory,扩充内存reallocateMemory,释放内存freeMemory

    线程调度

    包括线程的挂起,恢复,锁机制方法

    //取消阻塞线程 
    public native void unpark(Object thread); 
    //阻塞线程 
    public native void park(boolean isAbsolute, long time); 
    //获得对象锁(可重入锁) 
    @Deprecated 
    public native void monitorEnter(Object o); //释放对象锁 @Deprecated
    public native void monitorExit(Object o); //尝试获取对象锁 @Deprecated
    public native boolean tryMonitorEnter(Object o);

    锁机制相关的native方法已过时,不关注

    park:将一个线程挂起,调用park方法后,线程将一直阻塞直到超时或者中断等条件出现;

    unpark:终止一个挂起的线程,使其恢复正常

    典型应用:

    Java锁和同步器框架的核心类AbstractQueuedSynchronizer,通过调用LockSupport.park()LockSupport.unpark()

    实现线程的阻塞和唤醒,LockSupport的park、unpark方法实际是调用Unsafe的park、unpark方式来实现

    LockSupport先点名,下一篇上

    内存屏障

    有点熟悉,volatile也用到了内存屏障,用来禁止代码重排序,Unsafe的这几个方法也是为了禁止重排,理解上是一样的

    //内存屏障,禁止load操作重排序。屏障前的load操作不能被重排序到屏障后,屏障后的load操作不能被重排序到屏障前
    public native void loadFence();
    //内存屏障,禁止store操作重排序。屏障前的store操作不能被重排序到屏障后,屏障后的store操作不能被重排序到屏障前
    public native void storeFence();
    //内存屏障,禁止load、store操作重排序
    public native void fullFence();

    典型应用

    java8中读写锁的改进版本:StampedLock

    StampedLock.validate方法源码:

     StampedLock的典型用法是

    1、获取乐观读锁

    2、copy变量到工作内存

    3、锁状态检测

    其中第2和3步要保证不会发生重排序,因为按照执行逻辑,2必须在3之前发生,3处使用loadFence()加上内存屏障,保证顺序

    StampedLock类,后面介绍

    其他功能,碰到的位置比较少,不做介绍

    总结Unsafe类

    单例模式的体现;

    提供了很多native的不安全操作的方法;

    提供了三个CAS原子操作的API方法;

    提供了JUC下实现"锁"的基本类(Atomic,AQS,LockSupport)所需的线程调度和CAS方法

  • 相关阅读:
    Longest Valid Parentheses
    [转载]ios入门篇 -hello Word(1)
    EXTJS 4 动态grid
    Spring AOP JPA
    Jchart 演示
    HSQLDB JPA GeneratedValue
    Antlr 练习
    回火方程
    URL decode 解决中文目录的乱码问题
    Arduino IIC lcd1602
  • 原文地址:https://www.cnblogs.com/yb38156/p/14434497.html
Copyright © 2011-2022 走看看