继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

Java并发编程-无锁CAS与Unsafe类及其并发包Atomic

慕的地10843
关注TA
已关注
手记 1081
粉丝 200
获赞 962

执行函数:CAS(V,E,N)

其包含3个参数V表示要更新的变量E表示预期值N表示新值如果V值等于E值,则将V的值设为N。若V值和E值不同,则说明已经有其他线程做了更新,则当前线程什么都不做。通俗的理解就是CAS操作需要我们提供一个期望值,当期望值与当前线程的变量值相同时,说明还没线程修改该值,当前线程可以进行修改,也就是执行CAS操作,但如果期望值与当前线程不符,则说明该值已被其他线程修改,此时不执行更新操作,但可以选择重新读取该变量再尝试再次修改该变量,也可以放弃操作,原理图如下


webp

由于CAS操作属于乐观派,它总认为自己可以成功完成操作,当多个线程同时使用CAS操作一个变量时,只有一个会胜出,并成功更新,其余均会失败,但失败的线程并不会被挂起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作,这点从图中也可以看出来。基于这样的原理,CAS操作即使没有锁,同样知道其他线程对共享资源操作影响,并执行相应的处理措施。同时从这点也可以看出,由于无锁操作中没有锁的存在,因此不可能出现死锁的情况,也就是说无锁操作天生免疫死锁。

CPU指令对CAS的支持

或许我们可能会有这样的疑问,假设存在多个线程执行CAS操作并且CAS的步骤很多,有没有可能在判断V和E相同后,正要赋值时,切换了线程,更改了值。造成了数据不一致呢?答案是否定的,因为CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。

鲜为人知的指针: Unsafe类

Unsafe类存在于sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存,单从名称看来就可以知道该类是非安全的,毕竟Unsafe拥有着类似于C的指针操作,因此总是不应该首先使用Unsafe类,Java官方也不建议直接使用的Unsafe类,据说Oracle正在计划从Java 9中去掉Unsafe类,但我们还是很有必要了解该类,因为Java中CAS操作的执行依赖于Unsafe类的方法,注意Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应任务,关于Unsafe类的主要功能点如下:

内存管理,Unsafe类中存在直接操作内存的方法

//分配内存指定大小的内存 public native long allocateMemory(long bytes); //根据给定的内存地址address设置重新分配指定大小的内存 public native long reallocateMemory(long address, long bytes); //用于释放allocateMemory和reallocateMemory申请的内存 public native void freeMemory(long address); //将指定对象的给定offset偏移量内存块中的所有字节设置为固定值 public native void setMemory(Object o, long offset, long bytes, byte value); //设置给定内存地址的值 public native void putAddress(long address, long x); //获取指定内存地址的值 public native long getAddress(long address); //设置给定内存地址的long值 public native void putLong(long address, long x); //获取指定内存地址的long值 public native long getLong(long address); //设置或获取指定内存的byte值 public native byte  getByte(long address); public native void  putByte(long address, byte x); //其他基本数据类型(long,char,float,double,short等)的操作与putByte及getByte相同 //操作系统的内存页大小 public native int pageSize();

提供实例对象新途径。

  //传入一个对象的class并创建该实例对象,但不会调用构造方法public native Object allocateInstance(Class cls) throws InstantiationException;

类和实例对象以及变量的操作,主要方法如下

//获取字段f在实例对象中的偏移量 public native long objectFieldOffset(Field f); //静态属性的偏移量,用于在对应的Class对象中读写静态属性 public native long staticFieldOffset(Field f); //返回值就是f.getDeclaringClass() public native Object staticFieldBase(Field f); //获得给定对象偏移量上的int值,所谓的偏移量可以简单理解为指针指向该变量的内存地址, //通过偏移量便可得到该对象的变量,进行各种操作 public native int getInt(Object o, long offset); //设置给定对象上偏移量的int值 public native void putInt(Object o, long offset, int x); //获得给定对象偏移量上的引用类型的值 public native Object getObject(Object o, long offset); //设置给定对象偏移量上的引用类型的值 public native void putObject(Object o, long offset, Object x); //其他基本数据类型(long,char,byte,float,double)的操作与getInthe及putInt相同 //设置给定对象的int值,使用volatile语义,即设置后立马更新到内存对其他线程可见 public native void  putIntVolatile(Object o, long offset, int x); //获得给定对象的指定偏移量offset的int值,使用volatile语义,总能获取到最新的int值。 public native int getIntVolatile(Object o, long offset); //其他基本数据类型(long,char,byte,float,double)的操作与putIntVolatile及getIntVolatile相同,引用类型putObjectVolatile也一样。 //与putIntVolatile一样,但要求被操作字段必须有volatile修饰 public native void putOrderedInt(Object o,long offset,int x);

下面通过一个简单的Demo来演示上述的一些方法以便加深对Unsafe类的理解

public class UnSafeDemo {

    public  static  void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InstantiationException {

        // 通过反射得到theUnsafe对应的Field对象        Field field = Unsafe.class.getDeclaredField("theUnsafe");

        // 设置该Field为可访问        field.setAccessible(true);

        // 通过Field得到该Field对应的具体对象,传入null是因为该Field为static的        Unsafe unsafe = (Unsafe) field.get(null);

        System.out.println(unsafe);

        //通过allocateInstance直接创建对象        User user = (User) unsafe.allocateInstance(User.class);

        Class userClass = user.getClass();

        Field name = userClass.getDeclaredField("name");

        Field age = userClass.getDeclaredField("age");

        Field id = userClass.getDeclaredField("id");

        //获取实例变量name和age在对象内存中的偏移量并设置值        unsafe.putInt(user,unsafe.objectFieldOffset(age),18);

        unsafe.putObject(user,unsafe.objectFieldOffset(name),"android TV");

        // 这里返回 User.class,        Object staticBase = unsafe.staticFieldBase(id);

        System.out.println("staticBase:"+staticBase);

        //获取静态变量id的偏移量staticOffset        long staticOffset = unsafe.staticFieldOffset(userClass.getDeclaredField("id"));

        //获取静态变量的值        System.out.println("设置前的ID:"+unsafe.getObject(staticBase,staticOffset));

        //设置值        unsafe.putObject(staticBase,staticOffset,"SSSSSSSS");

        //获取静态变量的值        System.out.println("设置前的ID:"+unsafe.getObject(staticBase,staticOffset));

        //输出USER        System.out.println("输出USER:"+user.toString());

        long data = 1000;

        byte size = 1;//单位字节        //调用allocateMemory分配内存,并获取内存地址memoryAddress        long memoryAddress = unsafe.allocateMemory(size);

        //直接往内存写入数据        unsafe.putAddress(memoryAddress, data);

        //获取指定内存地址的数据        long addrData=unsafe.getAddress(memoryAddress);

        System.out.println("addrData:"+addrData);

        /**

         * 输出结果:

         sun.misc.Unsafe@6f94fa3e

         staticBase:class geym.conc.ch4.atomic.User

         设置前的ID:USER_ID

         设置前的ID:SSSSSSSS

         输出USER:User{name='android TV', age=18', id=SSSSSSSS'}

         addrData:1000

         */    }

}

class User{

    public User(){

        System.out.println("user 构造方法被调用");

    }

    private String name;

    private int age;

    private static String id="USER_ID";

    @Override

    public String toString() {

        return "User{" +

                "name='" + name + '\'' +

                ", age=" + age +'\'' +

                ", id=" + id +'\'' +

                '}';

    }

}

虽然在Unsafe类中存在getUnsafe()方法,但该方法只提供给高级的Bootstrap类加载器使用,普通用户调用将抛出异常,所以我们在Demo中使用了反射技术获取了Unsafe实例对象并进行相关操作。

public static Unsafe getUnsafe() {

      Class cc = sun.reflect.Reflection.getCallerClass(2);

      if (cc.getClassLoader() != null)

          throw new SecurityException("Unsafe");

      return theUnsafe;

  }

数组操作

//获取数组第一个元素的偏移地址public native int arrayBaseOffset(Class arrayClass);//数组中一个元素占据的内存空间,arrayBaseOffset与arrayIndexScale配合使用,可定位数组中每个元素在内存中的位置public native int arrayIndexScale(Class arrayClass);

CAS 操作相关 

CAS是一些CPU直接支持的指令,也就是我们前面分析的无锁操作,在Java中无锁操作CAS基于以下3个方法实现,在稍后讲解Atomic系列内部方法是基于下述方法的实现的。

//第一个参数o为给定对象,offset为对象内存的偏移量,通过这个偏移量迅速定位字段并设置或获取该字段的值,//expected表示期望值,x表示要设置的值,下面3个方法都通过CAS原子指令执行操作。public final native boolean compareAndSwapObject(Object o, long offset,Object expected, Object x);                                                                                                  public final native boolean compareAndSwapInt(Object o, long offset,int expected,int x);public final native boolean compareAndSwapLong(Object o, long offset,long expected,long x);

这里还需介绍Unsafe类中JDK 1.8新增的几个方法,它们的实现是基于上述的CAS方法,如下

//1.8新增,给定对象o,根据获取内存偏移量指向的字段,将其增加delta, //这是一个CAS操作过程,直到设置成功方能退出循环,返回旧值 public final int getAndAddInt(Object o, long offset, int delta) {

     int v;

     do {

         //获取内存中最新值         v = getIntVolatile(o, offset);

       //通过CAS操作     } while (!compareAndSwapInt(o, offset, v, v + delta));

     return v;

}//1.8新增,方法作用同上,只不过这里操作的long类型数据 public final long getAndAddLong(Object o, long offset, long delta) {

     long v;

     do {

         v = getLongVolatile(o, offset);

     } while (!compareAndSwapLong(o, offset, v, v + delta));

     return v;

}

//1.8新增,给定对象o,根据获取内存偏移量对于字段,将其 设置为新值newValue, //这是一个CAS操作过程,直到设置成功方能退出循环,返回旧值 public final int getAndSetInt(Object o, long offset, int newValue) {

     int v;

     do {

         v = getIntVolatile(o, offset);

     } while (!compareAndSwapInt(o, offset, v, newValue));

     return v;

}// 1.8新增,同上,操作的是long类型 public final long getAndSetLong(Object o, long offset, long newValue) {

     long v;

     do {

         v = getLongVolatile(o, offset);

     } while (!compareAndSwapLong(o, offset, v, newValue));

     return v;

}

//1.8新增,同上,操作的是引用类型数据 public final Object getAndSetObject(Object o, long offset, Object newValue) {

     Object v;

     do {

         v = getObjectVolatile(o, offset);

     } while (!compareAndSwapObject(o, offset, v, newValue));

     return v;

}

上述的方法我们在稍后的Atomic系列分析中还会见到它们的身影。

挂起与恢复 

将一个线程进行挂起是通过park方法实现的,调用 park后,线程将一直阻塞直到超时或者中断等条件出现。unpark可以终止一个挂起的线程,使其恢复正常。Java对线程的挂起操作被封装在 LockSupport类中,LockSupport类中有各种版本pack方法,其底层实现最终还是使用Unsafe.park()方法和Unsafe.unpark()方法

//线程调用该方法,线程将一直阻塞直到超时,或者是中断条件出现。  public native void park(boolean isAbsolute, long time);  //终止挂起的线程,恢复正常.java.util.concurrent包中挂起操作都是在LockSupport类实现的,其底层正是使用这两个方法,  public native void unpark(Object thread);

内存屏障

这里主要包括了loadFence、storeFence、fullFence等方法,这些方法是在Java 8新引入的,用于定义内存屏障,避免代码重排序,与Java内存模型相关,感兴趣的可以看博主的另一篇博文全面理解Java内存模型(JMM)及volatile关键字,这里就不展开了

//在该方法之前的所有读操作,一定在load屏障之前执行完成public native void loadFence();//在该方法之前的所有写操作,一定在store屏障之前执行完成public native void storeFence();//在该方法之前的所有读写操作,一定在full屏障之前执行完成,这个内存屏障相当于上面两个的合体功能public native void fullFence();

其他操作

//获取持有锁,已不建议使用@Deprecatedpublic native void monitorEnter(Object var1);//释放锁,已不建议使用@Deprecatedpublic native void monitorExit(Object var1);//尝试获取锁,已不建议使用@Deprecatedpublic native boolean tryMonitorEnter(Object var1);//获取本机内存的页数,这个值永远都是2的幂次方  public native int pageSize();  //告诉虚拟机定义了一个没有安全检查的类,默认情况下这个类加载器和保护域来着调用者类  public native Class defineClass(String name, byte[] b, int off, int len, ClassLoader loader, ProtectionDomain protectionDomain);  //加载一个匿名类public native Class defineAnonymousClass(Class hostClass, byte[] data, Object[] cpPatches);//判断是否需要加载一个类public native boolean shouldBeInitialized(Class c);//确保类一定被加载 public native  void ensureClassInitialized(Class c)

并发包中的原子操作类(Atomic系列)

通过前面的分析我们已基本理解了无锁CAS的原理并对Java中的指针类Unsafe类有了比较全面的认识,下面进一步分析CAS在Java中的应用,即并发包中的原子操作类(Atomic系列),从JDK 1.5开始提供了java.util.concurrent.atomic包,在该包中提供了许多基于CAS实现的原子操作类,用法方便,性能高效,主要分以下4种类型。

原子更新基本类型

原子更新基本类型主要包括3个类:

AtomicBoolean:原子更新布尔类型

AtomicInteger:原子更新整型

AtomicLong:原子更新长整型

这3个类的实现原理和使用方式几乎是一样的,这里我们以AtomicInteger为例进行分析,AtomicInteger主要是针对int类型的数据执行原子操作,它提供了原子自增方法、原子自减方法以及原子赋值方法等,鉴于AtomicInteger的源码不多,我们直接看源码

public classAtomicIntegerextendsNumberimplementsjava.io.Serializable{    private static final long serialVersionUID = 6214790243416807050L;

    // 获取指针类Unsafe    private static final Unsafe unsafe = Unsafe.getUnsafe();

    //下述变量value在AtomicInteger实例对象内的内存偏移量    private static final long valueOffset;

    static {

        try {

           //通过unsafe类的objectFieldOffset()方法,获取value变量在对象内存中的偏移           //通过该偏移量valueOffset,unsafe类的内部方法可以获取到变量value对其进行取值或赋值操作            valueOffset = unsafe.objectFieldOffset

                (AtomicInteger.class.getDeclaredField("value"));

        } catch (Exception ex) { throw new Error(ex); }

    }

   //当前AtomicInteger封装的int变量value    private volatile int value;

    public AtomicInteger(int initialValue) {

        value = initialValue;

    }

    public AtomicInteger() {

    }

   //获取当前最新值,    public final int get() {

        return value;

    }

    //设置当前值,具备volatile效果,方法用final修饰是为了更进一步的保证线程安全。    public final void set(int newValue) {

        value = newValue;

    }

    //最终会设置成newValue,使用该方法后可能导致其他线程在之后的一小段时间内可以获取到旧值,有点类似于延迟加载    public final void lazySet(int newValue) {

        unsafe.putOrderedInt(this, valueOffset, newValue);

    }

   //设置新值并获取旧值,底层调用的是CAS操作即unsafe.compareAndSwapInt()方法    public final int getAndSet(int newValue) {

        return unsafe.getAndSetInt(this, valueOffset, newValue);

    }

   //如果当前值为expect,则设置为update(当前值指的是value变量)    public final boolean compareAndSet(int expect, int update) {

        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);

    }

    //当前值加1返回旧值,底层CAS操作    public final int getAndIncrement() {

        return unsafe.getAndAddInt(this, valueOffset, 1);

    }

    //当前值减1,返回旧值,底层CAS操作    public final int getAndDecrement() {

        return unsafe.getAndAddInt(this, valueOffset, -1);

    }

   //当前值增加delta,返回旧值,底层CAS操作    public final int getAndAdd(int delta) {

        return unsafe.getAndAddInt(this, valueOffset, delta);

    }

    //当前值加1,返回新值,底层CAS操作    public final int incrementAndGet() {

        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;

    }

    //当前值减1,返回新值,底层CAS操作    public final int decrementAndGet() {

        return unsafe.getAndAddInt(this, valueOffset, -1) - 1;

    }

   //当前值增加delta,返回新值,底层CAS操作    public final int addAndGet(int delta) {

        return unsafe.getAndAddInt(this, valueOffset, delta) + delta;

    }

   //省略一些不常用的方法....}



作者:架构师springboot
链接:https://www.jianshu.com/p/82bc2d11ecb7


打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP