原子类型的使用&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 的正确性。