系列文章目录
一:计算机模型&volatile关键字详解
二:java中的锁体系
三:synchronized关键字详解
五:Atomic原子类与Unsafe魔法类详解
六:CAS下ABA问题及解决
文章目录
前言
通过前面的文章可知。volatile关键字和synchronize关键字可以帮助我们解决并发编程中的数据安全问题。在jdk自带的包中,也提供了一些原子类操作。
一、什么是原子操作?
原子(atom)本意是“不能被进一步分割的最小粒子”,而原子操作(atomic operation)意为”不可被中断的一个或一系列操作” 。在多处理器上实现原子操作就变得 有点复杂。本文让我们一起来聊一聊在Inter处理器和Java里是如何实现原子操作的。
二:Unsafe魔法类
Unsafe是位于sun.misc包下的一个类,主要提供一些用于执行低级别、不安全操作的 方法,如直接访问系统内存资源、自主管理内存资源等,这些方法在提升Java运行效率、增强Java语言底层资源操作能力方面起到了很大的作用。但由于Unsafe类使Java语言拥有了 类似C语言指针一样操作内存空间的能力,这无疑也增加了程序发生相关指针问题的风险。 在程序中过度、不正确使用Unsafe类会使得程序出错的概率变大,使得Java这种安全的语 言变得不再“安全”,因此对Unsafe的使用一定要慎重。 Unsafe类为一单例实现,提供静态方法getUnsafe获取Unsafe实例,当且仅当调用 getUnsafe方法的类为引导类加载器所加载时才合法,否则抛出SecurityException异常。
1、如何获取Unsafe实例?
-
从getUnsafe方法的使用限制条件出发,通过Java命令行命令-Xbootclasspath/a把 调用Unsafe相关方法的类A所在jar包路径追加到默认的bootstrap路径中,使得A被 引导类加载器加载,从而通过Unsafe.getUnsafe方法安全的获取Unsafe实例。
java Xbootclasspath/a:${path}
其中path为调用Unsafe相关方法的类所在jar包路径 -
通过反射获取单例对象Unsafe
public class UnsafeInstance {
public static Unsafe reflectGetUnsafe() {
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
return (Unsafe) field.get(null);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
2、Unsafe功能介绍
Unsafe提供的API大致可分为内存操作、CAS、Class相关、对象操作、线程调度、系 统信息获取、内存屏障、数组操作等几类
2.1、内存操作
//分配内存, 相当于C++的malloc函数
public native long allocateMemory(long bytes);
//扩充内存
public native long reallocateMemory(long address, long bytes); //释放内存
public native void freeMemory(long address);
//在给定的内存块中设置值
public native void setMemory(Object o, long offset, long bytes, byte value);
//内存拷贝
public native void copyMemory(Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes);
//获取给定地址值,忽略修饰限定符的访问限制。与此类似操作还有: getInt, getDouble,getLong,getChar等
public native Object getObject(Object o, long offset);
//为给定地址设置值,忽略修饰限定符的访问限制,与此类似操作还有: putInt,putDouble,putLong,putChar等
public native void putObject(Object o, long offset, Object x);
public native byte getByte(long address);
//为给定地址设置byte类型的值(当且仅当该内存地址为 allocateMemory分配 时,此方法结果才是确定的)
public native void putByte(long address, byte x);
通常,我们在Java中创建的对象都处于堆内内存(heap)中,堆内内存是由JVM 所管控的Java进程内存,并且它们遵循JVM的内存管理机制,JVM会采用垃圾回收机 制统一管理堆内存。与之相对的是堆外内存,存在于JVM管控之外的内存区域,Java 中对堆外内存的操作,依赖于Unsafe提供的操作堆外内存的native方法。
使用堆外内存的原因
- 对垃圾回收停顿的改善。由于堆外内存是直接受操作系统管理而不是JVM,所以 当我们使用堆外内存时,即可保持较小的堆内内存规模。从而在GC时减少回收停顿 对于应用的影响。
- 提升程序I/O操作的性能。通常在I/O通信过程中,会存在堆内内存到堆外内存的 数据拷贝操作,对于需要频繁进行内存间数据拷贝且生命周期较短的暂存数据,都建 议存储到堆外内存。
2.2、CAS相关
如下源代码释义所示,这部分主要为CAS相关操作的方法。
/*** CAS
- @param o 包含要修改field的对象
- @param offset 对象中某field的偏移量
- @param expected 期望值
- @param update 更新值
- @return true | false
- */
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
AtomicInteger的实现中,静态字段valueOffset即为字段value的内存偏 移地址,valueOffset的值在AtomicInteger初始化时,在静态代码块中通过Unsafe的 objectFieldOffset方法获取。在AtomicInteger中提供的线程安全方法中,通过字段 valueOffset的值可以定位到AtomicInteger对象中value的内存地址,从而可以根据CAS实 现对value字段的原子操作。
2.3、线程调度
包括线程挂起、恢复、锁机制等方法。
//取消阻塞线程
public native void unpark(Object thread);
//阻塞线程
public native void park(boolean isAbsolute, long time);
//获得对象锁(可重入锁)
@Deprecated
public native void monitorEnter(Object o);
//释放对象锁
@Deprecated
public native void monitorExit(Object o);
//尝试获取对象锁
@Deprecated
public native boolean tryMonitorEnter(Object o);
方法park、unpark即可实现线程的挂起与恢复,将一个线程进行挂起是通过park方 法实现的,调用park方法后,线程将一直阻塞直到超时或者中断等条件出现; unpark可以终止一个挂起的线程,使其恢复正常。
Java锁和同步器框架的核心类AbstractQueuedSynchronizer,就是通过调用 LockSupport.park()和LockSupport.unpark()实现线程的阻塞和唤醒的,而 LockSupport的park、unpark方法实际是调用Unsafe的park、unpark方式来实现。
2.4、内存屏障
在Java 8中引入,用于定义内存屏障(也称内存栅栏,内存栅障,屏障指令等,是一类 同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的 所有读写操作都执行后才可以开始执行此点之后的操作),避免代码重排序。
//内存屏障,禁止load操作重排序。屏障前的load操作不能被重排序到屏 障后,屏障后的load操作不能被重排序到屏障前
public native void loadFence();
//内存屏障,禁止store操作重排序。屏障前的store操作不能被重排序到屏障后, 屏障后的store操作不能被重排序到屏障前
public native void storeFence();
//内存屏障,禁止load、store操作重排序
public native void fullFence();
二:jdk提供的原子类
在Atomic包里一共有12个类,四种原子更新方式,分别是原子更新基本类型,原子更 新数组,原子更新引用和原子更新字段。Atomic包里的类基本都是使用Unsafe实现的包装 类。
- 基本类:AtomicInteger、AtomicLong、AtomicBoolean
- 数组类型:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
- 引用类型:AtomicReference、AtomicStampedRerence、AtomicMarkableReference
- 属性原子修改器(Updater):AtomicIntegerFieldUpdater、 AtomicLongFieldUpdater、AtomicReferenceFieldUpdater
1、基本类
这三个类原子都是在内部保存一个变量value,并且通过volatile关键字修饰保证数据的可见性。然后依赖Unsafe的CAS方法保证并发修改的正确性,再综合利用volatile与Unsafe的CAS方法返回结果组合使用利用循环保证更新线程安全和一定成功。
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger();
System.out.println("比较值与期望的是否一致,一致则更新 ");
atomicInteger.set(11);
atomicInteger.compareAndSet(12,14);
System.out.println(atomicInteger.get());
System.out.println("设置新值并返回最新的值 ");
atomicInteger.set(11);
System.out.println(atomicInteger.decrementAndGet());
System.out.println("先获取值,并设置新值 ");
atomicInteger.set(11);
System.out.println(atomicInteger.getAndSet(15));
System.out.println(atomicInteger.get());
System.out.println("先新增,并且返回新增到的值");
atomicInteger.set(11);
System.out.println(atomicInteger.incrementAndGet());
System.out.println(atomicInteger.get());
System.out.println("先返回值,然后再新增");
atomicInteger.set(11);
System.out.println(atomicInteger.getAndIncrement());
System.out.println(atomicInteger.get());
System.out.println("自定义计算");
atomicInteger.set(11);
IntBinaryOperator test1 = (a, b) -> a * b;
System.out.println(atomicInteger.accumulateAndGet(2,test1));
}
2、数组类型原子类
数组类型原子类也有三个AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray,AtomicIntegerArray与AtomicLongArray实际上和AtomicInteger与AtomicLong提供的功能差不多,只不过他们是提供一个数组的原子性,也就是他们提供的是一系列变量的原子性,而AtomicInteger与AtomicLong维护的是一个变量的原子性。
而AtomicReferenceArray与AtomicReference就和AtomicIntegerArray与AtomicInteger对比一样。
他们与单个原子类的区别是,再初始化的时候会初始化一个数组,每次修改都是修改指定数组索引的一个数据。逆向思维一下就相当于他们是在维护一个数组的原子性,而基础原子类是在维护单个变量的原子性。
3、引用类型原子类
引用类型的原子类也有三个AtomicReference、AtomicStampedReference、AtomicMarkableReference,主要是AtomicReference介绍下,另外两个原子类都是对AtomicReference的扩展,但是实际上AtomicReference和基础原子类一样,甚至我觉得可以归为基础类,因为它和基础类一样是维护一个变量value只不过类型是泛型,也就是说可以保存任意的类型,然后每次修改的时候比较value的引用是否一致。所以AtomicReference相当于是基础类型的扩展,它是维护所有对象,每次修改的时候都需要验证value的引用而基础类型是验证具体的值。
AtomicStampedReference是对 AtomicReference 的升级,它的实现是维护一个volatile 修饰的Pair类型变量,Pair保存有需要维护的对象和一个int类型的标记(就好像版本号),每次更新都是创建一个新的Pair对象,所有每次修改都要对象和标记同时满足才能成功,解决 CAS 的 ABA 问题。
AtomicMarkableReference与AtomicStampedReference类似,只不过Pair类中维护的表示是boolean类型,可以用于表示该对象已删除等场景。
public static void main(String[] args) {
AtomicReference atomicReference = new AtomicReference();
String str = "HELLO";
atomicReference.set(str);
System.out.println(atomicReference.accumulateAndGet("WORLD", (x, y) -> x + "__AAA__" + y));
}
4、属性原子修改器
有一些对象已经发布出去了无法修改,但是可能一开始的设计并没有考虑线程安全问题,现在再做修改要保证它其中一些属性的线程安全就可以利用字段更新器,主要有三个类AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater,提供的主要功能和AtomicInteger、AtomicLong、AtomicReference一样,只不过AtomicIntegerFieldUpdater是维护指定对象的指定属性的线程安全,AtomicInteger是维护它自己的value的线程安全的区别
static AtomicIntegerFieldUpdater aifu = AtomicIntegerFieldUpdater.newUpdater(Student.class,"old");
public static void main(String[] args) {
Student stu = new Student("小明",18);
System.out.println(aifu.getAndIncrement(stu));
System.out.println(aifu.get(stu));
}
@Data
static class Student{
private String name;
public volatile int old;
public Student(String name ,int old){
this.name = name;
this.old = old;
}
}