原子类型的使用&Unsafe&CAS

  在项目中也经常可以见到原子类型(AtomicXXX)的使用,而且AtomicXXX常用来代替基本类型或者基本类型的包装类型,因为其可以在不加同步锁的情况下保证线程安全(只对于原子操作)。

  下面以AtomicInteger为例子研究原子类型的线程安全性。

0. AtomicInteger 原子类型的基本使用

  其实在  AtomicInteger  里面将一些复合操作合并为原子操作,比如常见的"先改值,后取值"、"若相等则替换"、"获取到原值并赋予新值"等操作。

package cn.qlq.thread.nineteen;

import java.util.concurrent.atomic.AtomicInteger;

public class Demo2 {
    public static void main(String[] args) throws InterruptedException {
        AtomicInteger count = new AtomicInteger(0);
        System.out.println(count.incrementAndGet());// 先加后用,相当于++i
        System.out.println(count.getAndIncrement());// 先用后加,相当于i++
        System.out.println(count.intValue());
        System.out.println("===========");
        System.out.println(count.decrementAndGet());// 先减后用,相当于--i
        System.out.println(count.getAndDecrement());// 先用后减,相当于i--
        System.out.println(count.intValue());

        System.out.println("===========");
        count.set(10);
        System.out.println(count.addAndGet(5));// 先加后用
        System.out.println(count.getAndAdd(5));// 先用后加
        System.out.println(count.intValue());

        System.out.println("===========");
        System.out.println(count.getAndSet(100));// 获取原值,并用新值覆盖
        System.out.println(count.intValue());

        System.out.println("===========");
        System.out.println(count.compareAndSet(100, 850));// 如果是100就设为850
        System.out.println(count.compareAndSet(100, 800));// 如果是100就设为800
        System.out.println(count.intValue());
    }

}

结果:

1
1
2
===========
1
1
0
===========
15
15
20
===========
20
100
===========
true
false
850

 

1. AtomicInteger 原子类型的线程安全性

  AtomicInteger代替Integer和int的 ++效果实现线程安全。

package cn.qlq.thread.nineteen;

import java.util.concurrent.atomic.AtomicInteger;

public class Demo2 {
    public static void main(String[] args) throws InterruptedException {
        Runnable sync4 = new Sync4();
        for (int i = 0; i < 50; i++) {
            new Thread(sync4, "" + i).start();
        }
    }
}

class Sync4 implements Runnable {
    private AtomicInteger count = new AtomicInteger(0);

    public void run() {
        System.out.println(count.getAndIncrement() + "\tthreadName->" + Thread.currentThread().getName());
    }
}

 

原子类型也不一定是线程安全的:

package cn.qlq.thread.nineteen;

import java.util.concurrent.atomic.AtomicInteger;

public class Demo2 {
    public static void main(String[] args) throws InterruptedException {
        Sync5 sync4 = new Sync5();
        Thread[] threads = new Thread[5];
        for (int i = 0; i < 5; i++) {
            threads[i] = new Thread(sync4);
        }
        for (int i = 0; i < 5; i++) {
            threads[i].start();
        }
    }
}

class Sync5 implements Runnable {
    private AtomicInteger count = new AtomicInteger(0);

    public void run() {
        System.out.println("\tthreadName->" + Thread.currentThread().getName() + ",加了100后的值是" + count.addAndGet(100));
        count.addAndGet(1);
    }
}

结果:

threadName->Thread-0,加了100后的值是200
threadName->Thread-1,加了100后的值是100
threadName->Thread-2,加了100后的值是302
threadName->Thread-4,加了100后的值是403
threadName->Thread-3,加了100后的值是503

 

原因:

  从结果看出还是发生了线程非安全的问题。因为虽然addAndGet()方法是同步的,但是方法和方法的调用顺序却不是原子的。也就是同一个方法的两次调用addAndGet()中间可能发生线程非安全。解决办法仍然是加同步关键字。

 

2. AtomicInteger 原子类型分析

在分析 AtomicInteger  之前需要先分析  Unsafe 类。因为AtomicXXX中大量的用到了unsafe,而用的是unsafe的CAS操作。

2.1  Unsafe 类

Java无法直接访问底层操作系统,而是通过本地(native)方法来访问。不过尽管如此,JVM还是开了一个后门,JDK中有一个类Unsafe,它提供了硬件级别的原子操作

这个类尽管里面的方法都是public的,但是并没有办法使用它们,JDK API文档也没有提供任何关于这个类的方法的解释。

1、通过Unsafe类可以分配内存,可以释放内存;(这种方法分配的内存不占用heap内容,也需要手动回收)

类中提供的3个本地方法allocateMemory、reallocateMemory、freeMemory分别用于分配内存,扩充内存和释放内存。

public native long allocateMemory(long l);
public native long reallocateMemory(long l, long l1);
public native void freeMemory(long l);
2、可以定位对象某字段的内存位置,也可以修改对象的字段值,即使它是私有的;(根据字段的偏移量可以取值与设置值)---对于给定的filed,其内存地址偏移量是唯一的且是固定不变的。

例如:

package cn.qlq.thread.nineteen;

import java.lang.reflect.Field;

import sun.misc.Unsafe;

/**
 * Unsafe类的使用
 * Unsafe的直接内存访问:用Unsafe开辟的内存空间不占用Heap空间,当然也不具有自动内存回收功能。做到像C一样自由利用系统内存资源。
 * 
 * @author Administrator
 *
 */
public class Demo3 {
    public static void main(String[] args) throws Exception {
        test1();
    }

    public static long getLocation(Object object) throws Exception {
        Unsafe unsafe = getUnsafeInstance();
        Object[] array = new Object[] { object };
        long baseOffset = unsafe.arrayBaseOffset(Object[].class);// 能够获取数组第一个元素的偏移地址
        int addressSize = unsafe.addressSize();
        long location;
        switch (addressSize) {

        case 4:
            location = unsafe.getInt(array, baseOffset);
            break;
        case 8:
            location = unsafe.getLong(array, baseOffset);
            break;
        default:
            throw new Error("unsupported address size: " + addressSize);
        }
        return location;
    }

    private static void test1() throws Exception {
        Unsafe u = getUnsafeInstance();
        // 通过Unsafe 构造一个实例,即使构造方法是private声明的
        // Value value = (Value) u.allocateInstance(Value.class);
        Value value = new Value();
        System.out.println(value.getAge());

        // 静态成员遍历
        Field staticIntField = Value.class.getDeclaredField("age");
        long staticFieldOffset = u.staticFieldOffset(staticIntField);// 获取偏移量
        System.out.println("staticFieldOffset -> " + staticFieldOffset);
        u.putInt(Value.class, staticFieldOffset, 80);// 修改静态成员遍历的值
        System.out.println(value.getAge());

        // 普通成员修改(虽然是private修饰的,根据偏移量也可以取值与设置)
        Field field = Value.class.getDeclaredField("sex");
        int fieldOffset = u.fieldOffset(field);// 获取到偏移量
        System.out.println(u.getInt(value, fieldOffset));
        u.putInt(value, fieldOffset, 2);// 根据偏移量修改值
        System.out.println(value.getSex());
    }

    // 反射获取unsafe实例
    public static Unsafe getUnsafeInstance() throws Exception {
        Field f = Unsafe.class.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        Unsafe unsafe = (Unsafe) f.get(null);
        return unsafe;
    }
}

class Value {
    private static int age = 25;
    private int sex = 1;

    public static int getAge() {
        return age;
    }

    public static void setAge(int age) {
        Value.age = age;
    }

    public int getSex() {
        return sex;
    }

    public void setSex(int sex) {
        this.sex = sex;
    }
}

结果:

25
staticFieldOffset -> 88
80
1
2

3、挂起与恢复

4、CAS比较并交换操作(重要)

    public final native boolean compareAndSwapObject(Object arg0, long arg1, Object arg3, Object arg4);

    public final native boolean compareAndSwapInt(Object arg0, long arg1, int arg3, int arg4);

    public final native boolean compareAndSwapLong(Object arg0, long arg1, long arg3, long arg5);

  第一个参数是需要更新的对象,第二个参数是字段的偏移量,第三个是  希望field中存在的值,第四个是如果期望值expect与field的当前值相同,设置filed的值为这个新值。

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

  CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该位置的值。CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。” Java并发包(java.util.concurrent)中大量使用了CAS操作,涉及到并发的地方都调用了sun.misc.Unsafe类方法进行CAS操作。

例如:一个简单的CAS的例子

package cn.qlq.thread.nineteen;

import java.lang.reflect.Field;

import sun.misc.Unsafe;

/**
 * Unsafe类的使用
 * Unsafe的直接内存访问:用Unsafe开辟的内存空间不占用Heap空间,当然也不具有自动内存回收功能。做到像C一样自由利用系统内存资源。
 * 
 * @author Administrator
 *
 */
public class Demo4 {
    public static void main(String[] args) throws Exception {
        Value2 value2 = new Value2();
        System.out.println(compAndAwap(value2, 0, 5));
        System.out.println(compAndAwap(value2, 1, 5));
        System.out.println(value2.getSex());
    }

    public static boolean compAndAwap(Value2 value, int oldValue, int newValue) throws Exception {
        Unsafe u = getUnsafeInstance();

        // 普通成员修改(虽然是private修饰的,根据偏移量也可以取值与设置)
        Field field = Value.class.getDeclaredField("sex");
        int fieldOffset = u.fieldOffset(field);// 获取到偏移量

        int int1 = u.getInt(value, fieldOffset);// 判断旧的值与期望值是否相等
        if (int1 == oldValue) {
            u.putInt(value, fieldOffset, newValue);// 根据偏移量修改值
            return true;
        }

        return false;
    }

    // 反射获取unsafe实例
    public static Unsafe getUnsafeInstance() throws Exception {
        Field f = Unsafe.class.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        Unsafe unsafe = (Unsafe) f.get(null);
        return unsafe;
    }
}

class Value2 {
    private int sex = 1;

    public int getSex() {
        return sex;
    }

    public void setSex(int sex) {
        this.sex = sex;
    }
}

结果

false
true
5

2.2  AtomicInteger源码分析-以addAndGet(int)为例

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    // setup to use Unsafe.compareAndSwapInt for updates
    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); }
    }

    private volatile int value;
    /**
     * Atomically adds the given value to the current value.
     *
     * @param delta the value to add
     * @return the updated value
     */
    public final int addAndGet(int delta) {
        for (;;) {
            int current = get();
            int next = current + delta;
            if (compareAndSet(current, next))
                return next;
        }
    }
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
...
}

  可以看出其也是运用unsafe类的CAS进行比较并交换。由于对于给定的字段,其偏移量是固定且不变的,所以可以用静态代码块初始化 valueOffset (value字段的偏移量)。(valueOffset 是一个静态常量,并且在类加载时就被赋值了。那么这个类在编译后的字节码是一定的,但是在堆中存放 AtomicInteger 对象的首地址是随机的,所以这里的偏移应该是相对于对象实例的首地址)

  value 字段用volatile声明保存了内存可见性,由于for循环调用cas方法,所以当交换失败的时候会一直重新获取当前值并且进行cas操作。(CAS是原子操作,不可以被打断)。所以用这种机制保证了线程安全性。

需要注意的是:上面说了,AutomicXXX并不一定线程安全,如果一个方法中多次调用不能保证调用的顺序性。

2.3  AtomicBoolean源码分析

  AtomicBoolean实际是内部转换为int进行CAS操作。也就是内部维护一个int型的value(在0-1之间进行CAS操作)

public class AtomicBoolean implements java.io.Serializable {
    private static final long serialVersionUID = 4654671469794556979L;
    // setup to use Unsafe.compareAndSwapInt for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
      try {
        valueOffset = unsafe.objectFieldOffset
            (AtomicBoolean.class.getDeclaredField("value"));
      } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile int value;
    /**
     * Atomically sets to the given value and returns the previous value.
     *
     * @param newValue the new value
     * @return the previous value
     */
    public final boolean getAndSet(boolean newValue) {
        for (;;) {
            boolean current = get();
            if (compareAndSet(current, newValue))
                return current;
        }
    }
    public final boolean compareAndSet(boolean expect, boolean update) {
        int e = expect ? 1 : 0;
        int u = update ? 1 : 0;
        return unsafe.compareAndSwapInt(this, valueOffset, e, u);
    }
。。。
}

 

2.4  AtomicIntegerArray 的使用以及分析

  AtomicIntegerArray用于支持原子整形数组

package cn.qlq.thread.nineteen;

import java.util.concurrent.atomic.AtomicIntegerArray;

public class Demo5 {
    public static void main(String[] args) throws InterruptedException {
        AtomicIntegerArray array = new AtomicIntegerArray(new int[] { 0, 0 });
        System.out.println(array);
        System.out.println(array.getAndAdd(1, 2));
        System.out.println(array);
    }

}

结果:

[0, 0]
0
[0, 2]

 

源码分析:

public class AtomicIntegerArray implements java.io.Serializable {
    private static final long serialVersionUID = 2862133569453604235L;

    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final int base = unsafe.arrayBaseOffset(int[].class);
    private static final int shift;
    private final int[] array;

    static {
        int scale = unsafe.arrayIndexScale(int[].class);
        if ((scale & (scale - 1)) != 0)
            throw new Error("data type scale not a power of two");
        shift = 31 - Integer.numberOfLeadingZeros(scale);
    }

    private long checkedByteOffset(int i) {
        if (i < 0 || i >= array.length)
            throw new IndexOutOfBoundsException("index " + i);

        return byteOffset(i);
    }

    private static long byteOffset(int i) {
        return ((long) i << shift) + base;
    }
    /**
     * Atomically adds the given value to the element at index {@code i}.
     *
     * @param i the index
     * @param delta the value to add
     * @return the previous value
     */
    public final int getAndAdd(int i, int delta) {
        long offset = checkedByteOffset(i);
        while (true) {
            int current = getRaw(offset);
            if (compareAndSetRaw(offset, current, current + delta))
                return current;
        }
    }
    private boolean compareAndSetRaw(long offset, int expect, int update) {
        return unsafe.compareAndSwapInt(array, offset, expect, update);
    }
...
}

  由于涉及到了数组操作,所以对数组中元素进行CAS操作的时候需要获取到数组中对应元素的偏移量。而数组中元素的偏移量需要调用 arrayBaseOffset(class) 先获得数组的基偏移量,然后调用arrayIndexScale(class)获取每个元素占用的大小 ,这两个方法结合可以定位到数组中的每个元素与修改每个元素的值(基地址加上每个元素的大小)。

 

例如:

package cn.qlq.thread.nineteen;

import java.lang.reflect.Field;
import java.util.Arrays;

import sun.misc.Unsafe;

public class Demo5 {
    public static void main(String[] args) throws Exception {
        int[] array = new int[] { 1, 2, 3 };
        System.out.println(Arrays.toString(array));
        Unsafe unsafeInstance = getUnsafeInstance();
        // 获取数组的基地址
        int arrayBaseOffset = unsafeInstance.arrayBaseOffset(int[].class);
        System.out.println(arrayBaseOffset);

        // 比例值--每个元素占的大小
        int scale = unsafeInstance.arrayIndexScale(int[].class);
        System.out.println(scale);

        // 修改index为1元素的值为22
        unsafeInstance.putInt(array, arrayBaseOffset + 1 * scale, 22);

        // 采用元素加地址偏移量或者元素的值
        for (int i = 0; i < array.length; i++) {
            System.out.println(unsafeInstance.getInt(array, arrayBaseOffset + i * scale));
        }
    }

    // 反射获取unsafe实例
    public static Unsafe getUnsafeInstance() throws Exception {
        Field f = Unsafe.class.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        Unsafe unsafe = (Unsafe) f.get(null);
        return unsafe;
    }
}

结果:

[1, 2, 3]
16
4
1
22
3

 

补充:CAS 的ABA解决办法:

1.互斥同步锁synchronized

2.如果项目只在乎数值是否正确, 那么ABA 问题不会影响程序并发的正确性。

3. JUC 包提供了一个带有时间戳的原子引用类 AtomicStampedReference 来解决该问题,它通过控制变量的版本来保证 CAS 的正确性。

 

posted @ 2019-01-07 19:18  QiaoZhi  阅读(887)  评论(0编辑  收藏  举报