原子类型的使用&Unsafe&CAS

时间:2023-12-06 16:18:20

  在项目中也经常可以见到原子类型(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字段的偏移量)。

  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