目录
3.1 AtomicReferenceFieldUpdater
一,Unsafe类介绍
Unsafe类使Java拥有了像C语言的指针一样操作内存空间的能力.
1,获取Unsafe对象
在Unsafe源码中提供了一个静态方法来获取Unsafe对象,源码如下:
@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
但是我们不能直接使用,因为这个方法检测了类加载器,我们自己写的代码的类加载器是sun.misc.Launcher$AppClassLoader,不是代码中判断的那个,所以调用这个方法会抛出异常。
但是我们可以使用反射来获取这个对象的实例:
代码如下:
// 通过反射获取Unsafe实例对象
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe)f.get(null);
2,Unsafe提供的方法
该类提供了类似c/c++语言一样,直接对系统内存进行操作的方法。
例如:
对类和对象的属性的获取与修改;
直接分配内存空间,内存赋值,释放内存等操作;
提供了CAS操作;
线程调度:线程的挂起与唤醒、加锁和释放锁。
类的加载和内存屏障相关等操作方法。
下面来简单使用一下这个类的方法:
先提供一个测试类用于测试用:
类中提供了一个静态属性(platformVersion)和一个成员属性(fileCount)
class MyEneity{
private static String platformVersion = "1.0-pm4v1";
private int fileCount = 336;
public int getFileCount() {
return fileCount;
}
@Override
public String toString() {
return "MyEneity {" +
"platformVersion = " + platformVersion +
", fileCount = " + fileCount +
'}';
}
}
2.1 对类的静态属性的读与写
// 获取类属性的地址偏移量
long platformVersionOffset = unsafe.staticFieldOffset(MyEneity.class.getDeclaredField("platformVersion"));
// 获取类的属性的值
String platformVersion = unsafe.getObject(MyEneity.class, platformVersionOffset).toString();
// 修改类属性的值
unsafe.putObject(MyEneity.class, platformVersionOffset,"new-version-val");
2.2 对对象成员属性的读和写
// 获取对象属性的地址偏移量
long fileCountOffset = unsafe.objectFieldOffset(myEneity.getClass().getDeclaredField("fileCount"));
// 获取对象属性的值
int fileCount = unsafe.getInt(myEneity, fileCountOffset);
// 修改对象属性的值
unsafe.putInt(myEneity,fileCountOffset,0);
2.3 比较并替换操作
// 如果替换成功则返回true
unsafe.compareAndSwapInt(对象, 属性地址偏移量, 旧的值, 新的值);
简单写一个myEntity对象中fileCount的值线程安全的自增方法,如下:
private static void atomicIncrement(MyEneity myEneity,long valueOffset){
int oldValue ;
do {
oldValue = myEneity.getFileCount();
} while(!unsafe.compareAndSwapInt(myEneity, valueOffset, oldValue, oldValue + 1));
}
2.4 测试这些方法:
2.4.1 完整代码:
package com.salulu.t10;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class UnsafeTest {
private static final Unsafe unsafe;
static {
try {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
unsafe = (Unsafe)f.get(null);
} catch (Exception ex) { throw new Error(ex); }
}
public static void main(String[] args) throws InterruptedException, NoSuchFieldException {
MyEneity myEneity = new MyEneity();
// 获取对象属性的地址偏移量
long fileCountOffset = unsafe.objectFieldOffset(myEneity.getClass().getDeclaredField("fileCount"));
// 获取类属性的地址偏移量
long platformVersionOffset = unsafe.staticFieldOffset(MyEneity.class.getDeclaredField("platformVersion"));
System.out.println("fileCount的地址偏移量:"+fileCountOffset);
System.out.println("platformVersion的地址偏移量:"+platformVersionOffset);
// 获取对象属性的值
int fileCount = unsafe.getInt(myEneity, fileCountOffset);
// 获取类的属性的值
String platformVersion = unsafe.getObject(MyEneity.class, platformVersionOffset).toString();
System.out.println("fileCount="+fileCount);
System.out.println("platformVersion="+platformVersion);
System.out.println("修改前:"+myEneity.toString());
// 修改类属性的值
unsafe.putObject(MyEneity.class, platformVersionOffset,"new-version-val");
// 修改对象属性的值
unsafe.putInt(myEneity,fileCountOffset,0);
System.out.println("修改后:"+myEneity.toString());
// 两个线程,每个线程各自增10000次,结果应该为20000
Thread threadA = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
atomicIncrement(myEneity,fileCountOffset);
}
});
Thread threadB = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
atomicIncrement(myEneity,fileCountOffset);
}
});
// 启动两个线程
threadA.start();
threadB.start();
// 主线程等待两个线程执行结束
threadA.join();
threadB.join();
// 输出执行结果
// 如果fileCount的值为20000,说明使用cas操作实现了自增操作的线程安全
System.out.println("执行完自增方法后:"+myEneity.toString());
}
private static void atomicIncrement(MyEneity myEneity,long valueOffset){
int oldValue ;
do {
oldValue = myEneity.getFileCount();
} while(!unsafe.compareAndSwapInt(myEneity, valueOffset, oldValue, oldValue + 1));
}
}
class MyEneity{
private static String platformVersion = "1.0-pm4v1";
private int fileCount = 336;
public int getFileCount() {
return fileCount;
}
@Override
public String toString() {
return "MyEneity {" +
"platformVersion = " + platformVersion +
", fileCount = " + fileCount +
'}';
}
}
2.4.2,运行结果:
fileCount的地址偏移量:12
platformVersion的地址偏移量:104
fileCount=336
platformVersion=1.0-pm4v1
修改前:MyEneity {platformVersion = 1.0-pm4v1, fileCount = 336}
修改后:MyEneity {platformVersion = new-version-val, fileCount = 0}
执行完自增方法后:MyEneity {platformVersion = new-version-val, fileCount = 20000}
二,CAS操作:
1,CAS是什么?
CAS全称是Compare and Swap,即比较并交换的意思,其原理是通过cpu的原子指令来实现原子操作。
将获取存储在内存地址的原值和指定的内存地址进行比较,只有当他们相等时,交换指定的预期值和内存中的值,若不相等,则重新获取存储在内存地址的原值,这整个过程是原子操作。
2,使用CAS实现一个线程安全的自增功能:
代码中有详细的注释:
import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class MyAtomicOP {
//Unsafe类提供了硬件级别的原子操作
private static final Unsafe unsafe;
//目标Java变量中的目标属性的偏移地址。
private static final long valueOffset;
// 被操作的目标对象
private volatile int value;
public MyAtomicOP(int value){
this.value = value;
}
static {
try {
// 通过反射获取Unsafe实例对象
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
unsafe = (Unsafe)f.get(null);
// 获取value属性的内存偏移地址
valueOffset = unsafe.objectFieldOffset(MyAtomicOP.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
public int getValue() {
return value;
}
// 对valueOffset这个地址的值(也就是value的值)进行+1操作
void atomicIncrement(){
int oldValue ;
do {
// 获取this对象地址在valueOffset这个地址偏移量指向的值
oldValue = unsafe.getIntVolatile(this, valueOffset);
// 进行比较并替换操作,直到成功为止
// 参数说明:
// 第一个参数:包含要修改属性的对象 当前是MyAtomicOPTest对象实例,这里用this
// 第二个参数:this对象中整型需要操作的属性的地址偏移量 当前是MyAtomicOPTest对象的valueOffset属性
// 第三个参数:需要修改的属性的值 当前是MyAtomicOPTest对象的value属性
// 第四个参数:需要修改为多少,目标值
} while(!unsafe.compareAndSwapInt(this, valueOffset, oldValue, oldValue + 1));
}
}
测试一下:
public class MyAtomicMainTest {
public static void main(String[] args) throws InterruptedException {
MyAtomicOP unsafeTest = new MyAtomicOP(0);
// 创建两个线程调用atomicIncrement()方法对value对象进行操作
// 各调用10000次
// 两个线程执行完成后value的预期结果应该是20000
Thread threadA = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
unsafeTest.atomicIncrement();
}
});
Thread threadB = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
unsafeTest.atomicIncrement();
}
});
// 启动两个线程
threadA.start();
threadB.start();
// 主线程等待两个线程执行结束
threadA.join();
threadB.join();
// 输出执行结果
System.out.println(unsafeTest.getValue());
}
}
输出:
20000
3,CAS的一些问题
3.1 ABA问题
CAS操作是在更新值的时候检查下原值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,后来变成了B,然后又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。
这个问题看业务场景,如果是对于一个基本数据类型,CAS操作的中发生了ABA操作,但是业务没有影响,那么这个问题就不算问题;但是如果是需要感知数据的每一次改变,那么就需要解决ABA问题。
解决方式:给变量加上版本号,那么每次变量更新的时候把版本号加一,那么A->B->A 就会变成A1->B2->A3.
jdk提供了AtomicStampedReference类来解决ABA问题
3.2 如果多次操作失败对CPU开销大
在线程激烈争抢更新数据时,如果多次使用CAS来更新数据都失败,则会给CPU带来额外的性能开销。
3.3 一次原子操作只能更新一个共享变量的值
可以把多个数据放到同一个对象里,然后原子更新该对象:JDK提供了AtomicReference类来保证引用对象之间的原子性。
三、JDK提供的原子操作类
1,对基本类型的原子操作
1.1 int型的原子操作
AtomicInteger类
API介绍:
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(java.util.concurrent.atomic.AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
/**
* 使用给定的初始值创建一个新的AtomicInteger。
* @param initialValue
*/
public AtomicInteger(int initialValue) {
value = initialValue;
}
public AtomicInteger() {
}
/**
* 获取当前值。
*/
public final int get() {
return value;
}
/**
* 设置为给定值。
* @param newValue 新的值
*/
public final void set(int newValue) {
value = newValue;
}
/**
* 设置为给定值。
* @param newValue 新的值
*/
public final void lazySet(int newValue) {
unsafe.putOrderedInt(this, valueOffset, newValue);
}
/**
* 自动设置为给定值并返回旧值。
* @param newValue 新的值
* @return 修改前的值
*/
public final int getAndSet(int newValue) {
return unsafe.getAndSetInt(this, valueOffset, newValue);
}
/**
* 如果当前expect的值是预期值,自动将值设置为给定的更新值
* @param expect 期望值
* @param update 新值
* @return {@code true} 更新成功返回true
*/
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
/**
* 如果当前expect的值是预期值,自动将值设置为给定的更新值
* 可能会失败,并且不提供排序保证
* @param expect 期望值
* @param update 新值
* @return {@code true} 更新成功返回true
*/
public final boolean weakCompareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
/**
* 将当前值自增1
* @return 返回旧的值
*/
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
/**
* 将当前值原子递减1
* @return 返回旧的值
*/
public final int getAndDecrement() {
return unsafe.getAndAddInt(this, valueOffset, -1);
}
/**
* 原子的将给定值加上到当前值。
* @param delta 要增加的值
* @return 返回旧的值
*/
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
}
/**
* 将当前值原子自增1
* @return 返回更新后的值
*/
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
/**
* 将当前值原子自减1
* @return 返回更新后的值
*/
public final int decrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, -1) - 1;
}
/**
* 原子地将给定值添加到当前值。
*
* @param delta 要增加的值
* @return 返回更新后的值
*/
public final int addAndGet(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}
/**
* 使用应用给定函数的结果自动更新当前值,并返回以前的值。
* 当由于线程之间的争用而尝试更新失败时,会重新调用该函数。
* @param updateFunction 函数
* @return 返回旧值
*/
public final int getAndUpdate(IntUnaryOperator updateFunction) {
int prev, next;
do {
prev = get();
next = updateFunction.applyAsInt(prev);
} while (!compareAndSet(prev, next));
return prev;
}
/**
* 使用应用给定函数的结果自动更新当前值,并返回以前的值。
* 当由于线程之间的争用而尝试更新失败时,会重新调用该函数。
* @param updateFunction 函数
* @return 返回新的值
*/
public final int updateAndGet(IntUnaryOperator updateFunction) {
int prev, next;
do {
prev = get();
next = updateFunction.applyAsInt(prev);
} while (!compareAndSet(prev, next));
return next;
}
/**
* 使用将给定函数应用于当前值和给定值的结果自动更新当前值,并返回以前的值。
* 函数的第一个参数是当前值,第二个参数是给定的update。
* @param x 更新后的值
* @param accumulatorFunction 有两个参数的函数
* @return 旧的值
*/
public final int getAndAccumulate(int x,
IntBinaryOperator accumulatorFunction) {
int prev, next;
do {
prev = get();
next = accumulatorFunction.applyAsInt(prev, x);
} while (!compareAndSet(prev, next));
return prev;
}
/**
* 使用将给定函数应用于当前值和给定值的结果自动更新当前值,并返回以前的值。
* 函数的第一个参数是当前值,第二个参数是给定的要更新的值
* @param x 当前值
* @param accumulatorFunction 有两个参数的函数表达式
* @return 更新的值
*/
public final int accumulateAndGet(int x,
IntBinaryOperator accumulatorFunction) {
int prev, next;
do {
prev = get();
next = accumulatorFunction.applyAsInt(prev, x);
} while (!compareAndSet(prev, next));
return next;
}
public String toString() {
return Integer.toString(get());
}
public int intValue() {
return get();
}
public long longValue() {
return (long)get();
}
public float floatValue() {
return (float)get();
}
public double doubleValue() {
return (double)get();
}
}
1.2 long类型的原子操作
JDK提供AtomicLong类来操作
API与int型类似
1.3 boolean型的原子操作
JDK提供AtomicBoolean类来操作
API与int型类似
2,对数组类型的原子操作
2.1 任意类型的数组:
AtomicReferenceArray类
API:
/**
* 一个对象引用数组,其中的元素可以原子更新。
*/
public class AtomicReferenceArray<E> implements java.io.Serializable {
private static final long serialVersionUID = -6209656149925076980L;
private static final Unsafe unsafe;
private static final int base;
private static final int shift;
private static final long arrayFieldOffset;
private final Object[] array; // must have exact type Object[]
static {
try {
unsafe = Unsafe.getUnsafe();
arrayFieldOffset = unsafe.objectFieldOffset
(java.util.concurrent.atomic.AtomicReferenceArray.class.getDeclaredField("array"));
base = unsafe.arrayBaseOffset(Object[].class);
int scale = unsafe.arrayIndexScale(Object[].class);
if ((scale & (scale - 1)) != 0)
throw new Error("data type scale not a power of two");
shift = 31 - Integer.numberOfLeadingZeros(scale);
} catch (Exception e) {
throw new Error(e);
}
}
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;
}
/**
* 创建一个新的给定长度的AtomicReferenceArray,初始时所有元素为空。
* @param length 数组长度
*/
public AtomicReferenceArray(int length) {
array = new Object[length];
}
/**
* 创建一个新的AtomicReferenceArray,它与给定数组的长度相同,并复制其中的所有元素。
* @param array 要从中复制元素的数组
*/
public AtomicReferenceArray(E[] array) {
// Visibility guaranteed by final field guarantees
this.array = Arrays.copyOf(array, array.length, Object[].class);
}
/**
* 返回数组的长度。
*/
public final int length() {
return array.length;
}
/**
* 获取位置指定索引i处的当前值。
* @param i 索引
* @return 返回指定索引i处的当前值。
*/
public final E get(int i) {
return getRaw(checkedByteOffset(i));
}
@SuppressWarnings("unchecked")
private E getRaw(long offset) {
return (E) unsafe.getObjectVolatile(array, offset);
}
/**
* 将位于位置i的元素设置为给定值。
* @param i 角标
* @param newValue 新的值
*/
public final void set(int i, E newValue) {
unsafe.putObjectVolatile(array, checkedByteOffset(i), newValue);
}
/**
* 将位于位置i的元素设置为给定值(非实时)
* @param i 角标
* @param newValue 新的值
* @since 1.6
*/
public final void lazySet(int i, E newValue) {
unsafe.putOrderedObject(array, checkedByteOffset(i), newValue);
}
/**
* 原子地将位于位置i的元素设置为给定值并返回旧值。
* @param i 角标
* @param newValue 新的值
* @return 原来的值(旧值)
*/
@SuppressWarnings("unchecked")
public final E getAndSet(int i, E newValue) {
return (E)unsafe.getAndSetObject(array, checkedByteOffset(i), newValue);
}
/**
* 如果当前值是预期值,则原子地将位于位置i的元素设置为给定的更新值。
* @param i 角标
* @param expect 预期值
* @param update 新值
* @return {@code true} true为成功,false表示实际值与预期值不相等。
*/
public final boolean compareAndSet(int i, E expect, E update) {
return compareAndSetRaw(checkedByteOffset(i), expect, update);
}
private boolean compareAndSetRaw(long offset, E expect, E update) {
return unsafe.compareAndSwapObject(array, offset, expect, update);
}
/**
* 如果当前值是预期值,则原子地将位于位置i的元素设置为给定的更新值。
* 可能会失败,并且不提供排序保证
* @param i 角标
* @param expect 预期值
* @param update 新值
* @return {@code true}
*/
public final boolean weakCompareAndSet(int i, E expect, E update) {
return compareAndSet(i, expect, update);
}
/**
* 原子地用应用给定函数的结果更新索引i处的元素,返回之前的值。
* 当由于线程之间的争用而尝试更新失败时,会重新调用该函数。
* @param i 角标
* @param updateFunction 函数表达式
* @return 旧的值
*/
public final E getAndUpdate(int i, UnaryOperator<E> updateFunction) {
long offset = checkedByteOffset(i);
E prev, next;
do {
prev = getRaw(offset);
next = updateFunction.apply(prev);
} while (!compareAndSetRaw(offset, prev, next));
return prev;
}
/**
* 原子地用应用给定函数的结果更新索引i处的元素,返回之前的值。
* 当由于线程之间的争用而尝试更新失败时,会重新调用该函数。
* @param i 角标
* @param updateFunction 函数表达式
* @return 新的值
*/
public final E updateAndGet(int i, UnaryOperator<E> updateFunction) {
long offset = checkedByteOffset(i);
E prev, next;
do {
prev = getRaw(offset);
next = updateFunction.apply(prev);
} while (!compareAndSetRaw(offset, prev, next));
return next;
}
/**
* 原子地用应用给定函数的结果更新索引i处的元素,返回之前的值。
* 当由于线程之间的争用而尝试更新失败时,会重新调用该函数。
* 函数的第一个参数是索引i处的当前值,第二个参数是给定的新值。
* @param i 角标
* @param x 新的值
* @param accumulatorFunction
* @return 旧的值
*/
public final E getAndAccumulate(int i, E x,
BinaryOperator<E> accumulatorFunction) {
long offset = checkedByteOffset(i);
E prev, next;
do {
prev = getRaw(offset);
next = accumulatorFunction.apply(prev, x);
} while (!compareAndSetRaw(offset, prev, next));
return prev;
}
/**
* 原子地用应用给定函数的结果更新索引i处的元素,返回更新后的值。
* 当由于线程之间的争用而尝试更新失败时,会重新调用该函数。
* 函数的第一个参数是索引i处的当前值,第二个参数是给定的新值。
* @param i 角标
* @param x 新的值
* @param accumulatorFunction a side-effect-free function of two arguments
* @return 新值
*/
public final E accumulateAndGet(int i, E x,
BinaryOperator<E> accumulatorFunction) {
long offset = checkedByteOffset(i);
E prev, next;
do {
prev = getRaw(offset);
next = accumulatorFunction.apply(prev, x);
} while (!compareAndSetRaw(offset, prev, next));
return next;
}
public String toString() {
int iMax = array.length - 1;
if (iMax == -1)
return "[]";
StringBuilder b = new StringBuilder();
b.append('[');
for (int i = 0; ; i++) {
b.append(getRaw(byteOffset(i)));
if (i == iMax)
return b.append(']').toString();
b.append(',').append(' ');
}
}
/**
* 从流重新构造实例(即反序列化)。
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException,
java.io.InvalidObjectException {
// Note: This must be changed if any additional fields are defined
Object a = s.readFields().get("array", null);
if (a == null || !a.getClass().isArray())
throw new java.io.InvalidObjectException("Not array type");
if (a.getClass() != Object[].class)
a = Arrays.copyOf((Object[])a, Array.getLength(a), Object[].class);
unsafe.putObjectVolatile(this, arrayFieldOffset, a);
}
}
2.2 int数组的原子操作
AtomicIntegerArray类
API与AtomicReferenceArray类似
2.3 long类型数组的原子操作
AtomicLongArray类
API与AtomicReferenceArray类似
3,对引用类型的原子操作
3.1 AtomicReferenceFieldUpdater
给定对象的字段设置为给定的更新值,保存执行的原子性。
说明:一个基于反射的实用程序,允许对指定类的指定引用字段进行原子更新。这个类设计用于原子数据结构中,在这种结构中,同一个节点的几个引用字段独立地服从原子更新。
这个类中的ompareAndSet方法的保证比其他原子类的要弱。因为这个类不能确保字段的所有使用都适合于原子访问的目的,所以它只能保证在同一个Atomic对象上调用compareAndSet和set时的原子性。
API参考其他Atomic类
3.2 AtomicReference
可以原子更新对象的引用。
API参考其他Atomic类