java并发基础篇-Unsafe和原子操作

/ java并发基础篇 / 没有评论 / 70浏览

前言

本篇是java并发基础篇第一篇的内容,主要是介绍Unsafe方法一些使用情况,都是以例子的形式进行展示的。源代码版本为jdk1.8

Unsafe基础类

sun.misc.Unsafe是一个低级别的java类,一般情况下,不建议直接使用,因为他可以直接开辟内存,但是却不受虚拟机的管理,容易造成风险,而且他的很多api都是偏向底层均是本地方法,所以版本的改动很频繁。

当然也正是由于他是偏底层的类库,可以实现很多平常不容易实现功能,java的并发库concurrent,大部分的功能都是它提供的实现。

基本使用

Unsafe的提供的api大致有几类

偏移地址的主要方法有两个(其实还有好几个)arrayBaseOffset/objectFieldOffset。获取值的有getInt(object,offset),getLong(object,offset) 等等类似的

  @Test
    public void testArray() {
        int[] ints = new int[3];
        ints[0] = 2;
        ints[1] = 3;
        int offset = getUnsafe().arrayBaseOffset(int[].class); 获取int数组第一个元素的位置
        int i = getUnsafe().getInt(ints, (long) offset);
        int j = getUnsafe().getInt(ints, (long) offset + offset);
        System.out.println("get array[0] value " + i + " array[1] value " + j);
    }

输出结果:
get array[0] value 2 array[1] value 1

第二个元素的值是不对的~没搞懂,可能偏移位置不能简单的相加把

看一个对象的获取方式

    public class Student {
        private String name;
        private int age;
        public Student(String name, int age) {
            this.name = name;
            this.age = age;
        }
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }

    public class Teacher {
        private Student student;
        private int sex;

        public Teacher(Student student, int sex) {
            this.student = student;
            this.sex = sex;
        }
        @Override
        public String toString() {
            return "Teacher{" +
                    "student=" + student +
                    ", sex=" + sex +
                    '}';
        }
    }
    
    @Test
    public void testObject() {
        Student student = new Student("郁金涛", 20);
        Teacher teacher = new Teacher(student, 2);
        try {
            Field nameField = Student.class.getDeclaredField("name");
            long offset = getUnsafe().objectFieldOffset(nameField);
            Field studentField = Teacher.class.getDeclaredField("student");
            long studentOffset = getUnsafe().objectFieldOffset(studentField);
            System.out.println("student name " + getUnsafe().getObject(student, offset));
            System.out.println("student name " + getUnsafe().getObject(teacher, studentOffset));
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }
    
输出结果:
student name 郁金涛
student Student{name='郁金涛', age=20}

cas基本含义,三个参数,一个当前内存值V、旧的预期值A、即将更新的值B,当且仅当预期值A和内存值V相同时,将内存值修改为B并返回true,否则什么都不做,并返回false

主要有三个方法compareAndSwapObject/compareAndSwapInt/compareAndSwapLong。原子的操作类(AtomicInteger等)的实现都是靠它实现的。

    /**
    * var1 操作对象
    * var2 是对象某个属性的偏移地址
    * var4 想要设置的值,更新值B
    * var5 当前线程的获取到的value值。旧的预期值A
    * 
    * 这个方法是核心方法,如果在多线程的环境下,如果当前的内存值和var4不想等的话,那么这个方法会返回false,如果相等,则把值设置为var5,并返回true
    */
    public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

cas一个明显问题是BAB的问题。 如果变量V初次读取的时候是A,并且在准备赋值的时候检查到它仍然是A,那能说明它的值没有被其他线程修改过了吗?

如果在这段期间曾经被改成B,然后又改回A,那CAS操作就会误认为它从来没有被修改过。针对这种情况,java并发包中提供了一个带有标记的原子引用类AtomicStampedReference,它可以通过控制变量值的版本来保证CAS的正确性

这个类型的方法很多,而且基本上AtomicInteger/Long等等原子操作类都是基于Unsafe来实现的。

以AtomicInteger的getAndSet方法为例。

省略部分源码

    AtomicInteger.java
    ...
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }
    ...
    
    /**
    * newvalue是新值
    */
    public final int getAndSet(int newValue) {
        return unsafe.getAndSetInt(this, valueOffset, newValue);
    }
    
    Unsafe.java
    
    /**
    * var1是操作的对象
    * var2是对象某个属性的偏移地址-这里指的是AtomicInteger的value字段的偏移地址
    * var4就是新的值了
    */
    public final int getAndSetInt(Object var1, long var2, int var4) {
        int var5;
        do {
            //获取当前的对象AtomicInteger的value值
            var5 = this.getIntVolatile(var1, var2);
        //通过不停的循环,如果设置value成功的话,才跳出循环,并把最终的结果返回
        } while(!this.compareAndSwapInt(var1, var2, var5, var4));
        return var5;
    }
    

可以看出,AtomicInteger的getAndSet会直接调用unsafe的getAndSetInt方法,这个方法用到了CAS自旋。也就是不停的CAS,保证多线程更改value的正确性。

CAS自旋是并发库一种常见的策略,会耗费一定的cpu性能。其实就是不停的比较当前线程的值,和内存真实值是否一致,如果一致才会进行操作。

其他的方法都是类似的,有些会直接调用unsafe的compareAndSwapInt方法,但是不保证操作的成功性。如果要保证成功性就要使用自旋

主要有allocateMemory,copyMemory,freeMemory,getAddress,getInt,putInt等等。

慎重使用开辟内存方法,因为他们的内存不受虚拟机控制

大数组的实现。由于数组的大小的最大值是int的最大值。我们可以通过自己开辟内存方法,来建立一个超大的数组

public static class SuperIntArray {
        long size;
        long address; //获取分配内存的起始地址

        public SuperIntArray(long size) {
            this.size = size;
            address = getUnsafe().allocateMemory(size * (4));
        }

        public void setInt(long index, int value) {
            Assert.assertEquals(false, isRelease());
            getUnsafe().putInt(address + index * 4, value);
        }

        public int getInt(long index) {
            Assert.assertEquals(false, isRelease());
            return getUnsafe().getInt(address + index * 4);
        }
        private boolean isRelease() {
            return address == 0;
        }
        //释放内存
        public void release() {
            Assert.assertEquals(false, isRelease());
            getUnsafe().freeMemory(address);
            address = 0;
        }
    }

    @Test
    public void testSuperIntArray() {
        SuperIntArray superIntArray = new SuperIntArray((long) Integer.MAX_VALUE * 2);
        for (int i = 0; i < 10; i++) {
            superIntArray.setInt(i, i * 100);
        }
        long value = 0;
        for (int i = 0; i < 20; i++) {
            value += superIntArray.getInt(i);
        }

        System.out.println("super array sum " + value);
        superIntArray.setInt((long) Integer.MAX_VALUE + 3, 20);
        System.out.println("super array  " + ((long) Integer.MAX_VALUE + 3) + " value " + value);
        superIntArray.release();

    }
    
输出结果:
super array sum 4500
super array  2147483650 value 20

这些api是由c++/c底层malloc/free等api实现的,所以可能会发生意想不到的错误

原子操作类

原子操作类在并发包里大概4种,

  1. 原子更新基本类型类:AtomicBoolean/Integer/Long
  2. 原子更新数组类: AtomicArrayLong/Integer/Reference
  3. 原子更新对象引用类:AtomicReference/AtomicReferenceFieldUpdater/AotmicMarableReference(带有标记位的引用类型)
  4. 原子更新字段类:AtomicIntegerFieldUpdater/AtomicLongFieldUpdater/AtomicStampedReference

每个类的用法都差不多,AtomicReferenceFieldUpdater为例,使用上面的Teacher和student

    @Test
    public void testAtomicReference() {
        Student god = new Student("god", 1);
        Student demon = new Student("demon", 2);
        Teacher teacher = new Teacher(god, 3);
        AtomicReferenceFieldUpdater fieldUpdater = AtomicReferenceFieldUpdater.newUpdater(Teacher.class, Student.class, "student");
        Object old = fieldUpdater.getAndSet(teacher, demon);
        System.out.println("updater " + teacher + " old value " + old);
    }
    
输出:
updater Teacher{student=Student{name='demon', age=2}, sex=3} old value Student{name='god', age=1}

结束语

本文只是介绍一部分unsafe的使用方法,还有一些方法没有介绍使用,但是很多方法的内容都是大同小异的,就不一一介绍了