大厂之路一由浅入深、并行基础、源码分析一Unsafe类、varHandle类、无锁CAS、及其并发包Atomic
文章部分引用于:点击!!!!!Unsafe通过操作系统的相关知识我们知道,java无法直接访问底层的操作系统(中间还有虚拟机(JVM)),而是通过本地(Native)方法来访问底层的。但是,虚拟机也有让java直接访问的方法,那就是JDK中的一个类Unsafe,Unsafe提供了硬件级的原子操作。这个类尽管里面的方法都是public的,但是并没有办法使用它们,JDK API文档也没有提供任何关于这
·
- 文章部分引用于:点击!!!!!
- 文章部分引用于:点击!!!!!
- 文章部分引用于:点击,尤其Unsafe部分!!!!!
- 文章部分引用于:点击!!!!!
- 如标题中所说,无锁CAS,那什么是无锁呢?说到无锁,又要提到乐观派和悲观派。
- 乐观派:认为事情总是往好的方向发展,基本不会发生坏的事情 (无锁)。
- 悲观派:认为事情总是往坏的方向发展, 需要及时控制,虽然这种往坏的方向发展的概率非常小 (加锁)。
- 这两种派系映射到并发编程中就如同加锁与无锁的策略。
- 加锁:是一种悲观策略,因为对于加锁的并发程序来说,它们总是认为每次访问共享资源时总会发生冲突,因此必须对每一次数据操作实施加锁策略。
- 无锁:是一种乐观策略,总是假设对共享资源的访问没有冲突,线程可以不停执行,无需加锁,无需等待,一旦发现冲突,无锁策略则采用一种称为 CAS的技术来保证线程执行的安全性,这项CAS技术就是无锁策略实现的关键。
- Unsafe
- 通过操作系统的相关知识我们知道,java无法直接访问底层的操作系统(中间还有虚拟机(JVM)),而是 通过本地(Native)方法来访问底层的。
- 但是,虚拟机也有让java直接访问的方法,那就是JDK中的一个类Unsafe,该类中的方法都是native修饰的 , Unsafe提供了硬件级的原子操作。
- 这个类尽管里面的方法都是public的,但是并没有办法使用它们,JDK API文档也没有提供任何关于这个类的方法的解释。总而言之,对于Unsafe类的使用都是受限制的,只有授信的代码才能获得该类的实例,当然JDK库里面的类是可以随意使用的。
Unsafe类
存在于jdk.internal.misc
包中(如下图),其内部方法操作 可以像C的指针一样直接操作内存,单从名称看来就可以知道该类是非安全的,毕竟Unsafe拥有着类似于C的指针操作,因此总是不应该首先使用Unsafe类,Java官方也不建议直接使用的Unsafe类,据说Oracle正在计划从Java 9中去掉Unsafe类,但我们还是很有必要了解该类,因为Java中 CAS操作的执行依赖于Unsafe类的方法。
dassdsdsdsddasdasd- 注意Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应任务,也就是说Unsafe提供了硬件级别的操作,比如说获取某个属性在内存中的位置,修改对象的字段值,即使它是私有的,不过Java本身就是为了屏蔽底层的差异,对于一般的开发而言也很少会有这样的需求。
- 关于Unsafe类的主要功能点如下:
- 内存管理,Unsafe类中存在直接操作内存的方法:
//分配内存指定大小的内存
public native long allocateMemory(long bytes);
//根据给定的内存地址address设置重新分配指定大小的内存(一般用于扩充)
public native long reallocateMemory(long address, long bytes);
//用于释放allocateMemory和reallocateMemory申请的内存
public native void freeMemory(long address);
//将指定对象的给定offset偏移量内存块中的所有字节设置为固定值
public native void setMemory(Object o, long offset, long bytes, byte value);
//设置给定内存地址的值
public native void putAddress(long address, long x);
//获取指定内存地址的值
public native long getAddress(long address);
//设置给定内存地址的long值
public native void putLong(long address, long x);
//获取指定内存地址的long值
public native long getLong(long address);
//设置获取指定内存的byte值
public native byte getByte(long address);
//获取指定内存的byte值
public native void putByte(long address, byte x);
//其他基本数据类型(long,char,float,double,short等)的操作与putByte及getByte相同
//操作系统的内存页大小
public native int pageSize();
- 提供实例对象新途径:
//传入一个对象的class并创建该实例对象,但不会调用构造方法
public native Object allocateInstance(Class cls) throws InstantiationException;
- 类和实例对象以及变量的操作,主要方法如下:
//获取字段f在实例对象中的偏移量
public native long objectFieldOffset(Field f);
//静态属性的偏移量,用于在对应的Class对象中读写静态属性
public native long staticFieldOffset(Field f);
//返回值就是f.getDeclaringClass()
public native Object staticFieldBase(Field f);
//获得给定对象偏移量上的int值,所谓的偏移量可以简单理解为指针指向该变量的内存地址,
//通过偏移量便可得到该对象的变量,进行各种操作
public native int getInt(Object o, long offset);
//设置给定对象上偏移量的int值
public native void putInt(Object o, long offset, int x);
//获得给定对象偏移量上的引用类型的值
public native Object getObject(Object o, long offset);
//设置给定对象偏移量上的引用类型的值
public native void putObject(Object o, long offset, Object x);
//其他基本数据类型(long,char,byte,float,double)的操作与getInt及putInt相同
//设置给定对象的int值,使用volatile语义,即设置后立马更新到内存对其他线程可见
public native void putIntVolatile(Object o, long offset, int x);
//获得给定对象的指定偏移量offset的int值,使用volatile语义,总能获取到最新的int值。
public native int getIntVolatile(Object o, long offset);
//其他基本数据类型(long,char,byte,float,double)的操作与putIntVolatile及getIntVolatile相同,引用类型
putObjectVolatile也一样。
//与putIntVolatile一样,但要求被操作字段必须有volatile修饰
public native void putOrderedInt(Object o,long offset,int x);
- 数组操作
//获取数组第一个元素的偏移地址
public native int arrayBaseOffset(Class paramClass)
//数组中一个元素占据的内存空间,arrayBaseOffset与arrayIndexScale配合使用,可定位数组中每个元素在内存中的位置
public native int arrayIndexScale(Class paramClass);
- CAS 操作相关
CAS是一些CPU直接支持的指令,也就是我们前面分析的无锁操作,在Java中无锁操作CAS基于以下3个方法实现,在稍后讲解Atomic系列内部方法是基于下述方法的实现的。
//第一个参数o为给定对象,offset为对象内存的偏移量,通过这个偏移量迅速定位字段并设置或获取该字段的值,
//expected表示期望值,x表示要设置的值,下面3个方法都通过CAS原子指令执行操作。
public final native boolean compareAndSwapObject(Object o, long offset,Object expected, Object x);
public final native boolean compareAndSwapInt(Object o, long offset,int expected,int x);
public final native boolean compareAndSwapLong(Object o, long offset,long expected,long x);
- 这里还需介绍 Unsafe类中JDK 1.8新增的几个方法,它们的实现是基于上述的CAS方法,如下
//1.8新增,给定对象o,根据获取内存偏移量指向的字段,将其增加delta,
//这是一个CAS操作过程,直到设置成功方能退出循环,返回旧值
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
//获取内存中最新值
v = getIntVolatile(o, offset);
//通过CAS操作
} while (!compareAndSwapInt(o, offset, v, v + delta));
return v;
}
//1.8新增,方法作用同上,只不过这里操作的long类型数据
public final long getAndAddLong(Object o, long offset, long delta) {
long v;
do {
v = getLongVolatile(o, offset);
} while (!compareAndSwapLong(o, offset, v, v + delta));
return v;
}
//1.8新增,给定对象o,根据获取内存偏移量对于字段,将其 设置为新值newValue,
//这是一个CAS操作过程,直到设置成功方能退出循环,返回旧值
public final int getAndSetInt(Object o, long offset, int newValue) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!compareAndSwapInt(o, offset, v, newValue));
return v;
}
// 1.8新增,同上,操作的是long类型
public final long getAndSetLong(Object o, long offset, long newValue) {
long v;
do {
v = getLongVolatile(o, offset);
} while (!compareAndSwapLong(o, offset, v, newValue));
return v;
}
//1.8新增,同上,操作的是引用类型数据
public final Object getAndSetObject(Object o, long offset, Object newValue) {
Object v;
do {
v = getObjectVolatile(o, offset);
} while (!compareAndSwapObject(o, offset, v, newValue));
return v;
}
- 挂起与恢复
将一个线程进行挂起是通过park方法实现的,调用 park后,线程将一直阻塞直到超时或者中断等条件出现。unpark可以终止一个挂起的线程,使其恢复正常。Java对线程的挂起操作被封装在 LockSupport类中,LockSupport类中有各种版本park方法,其底层实现最终还是使用Unsafe.park()方法和Unsafe.unpark()方法
//线程调用该方法,线程将一直阻塞直到超时,或者是中断条件出现。
public native void park(boolean isAbsolute, long time);
//终止挂起的线程,恢复正常.java.util.concurrent包中挂起操作都是在LockSupport类实现的,其底层正是使用这两个方法,
public native void unpark(Object thread);
- 内存屏障
这里主要包括了loadFence、storeFence、fullFence等方法,这些方法是在Java 8新引入的,用于 定义内存屏障,避免代码重排序 ,与Java内存模型相关。
//在该方法之前的所有读操作,一定在load屏障之前执行完成
public native void loadFence();
//在该方法之前的所有写操作,一定在store屏障之前执行完成
public native void storeFence();
//在该方法之前的所有读写操作,一定在full屏障之前执行完成,这个内存屏障相当于上面两个的合体功能
public native void fullFence();
- 其他操作
//获取持有锁,已不建议使用,synchronized块底层用的就是monitorEnter、monitorExit
@Deprecated
public native void monitorEnter(Object var1);
//释放锁,已不建议使用
@Deprecated
public native void monitorExit(Object var1);
//尝试获取锁,已不建议使用
@Deprecated
public native boolean tryMonitorEnter(Object var1);
//获取本机内存的页数,这个值永远都是2的幂次方
public native int pageSize();
//告诉虚拟机定义了一个没有安全检查的类,默认情况下这个类加载器和保护域来着调用者类
public native Class defineClass(String name, byte[] b, int off, int len, ClassLoader loader, ProtectionDomain protectionDomain);
//加载一个匿名类
public native Class defineAnonymousClass(Class hostClass, byte[] data, Object[] cpPatches);
//判断是否需要加载一个类
public native boolean shouldBeInitialized(Class<?> c);
//确保类一定被加载
public native void ensureClassInitialized(Class<?> c)
- CAS
CAS,Compare and Swap
即比较并交换,设计并发算法时常用到的一种技术- cas是一种基于锁的操作,而且是乐观锁
- 在java中锁分为乐观锁和悲观锁:
- 悲观锁是将资源锁住,等一个之前获得锁的线程释放锁之后,下一个线程才可以访问(比如synchronized)。
- 乐观锁采取了一种宽泛的态度,通过某种方式不加锁来处理资源,比如通过给记录 加version来获取数据,性能较悲观锁有很大的提高。
- 重要性: java.util.concurrent包全完建立在CAS之上,没有CAS也就没有此包,可见CAS的重要性。
- 用法:当前的处理器基本都支持CAS,只不过不同的厂家的实现不一样罢了。CAS有三个操作数:内存值V、旧的预期值E、新值N,当且仅当E==V时,将内存值修改为N并返回true。CAS是通过无限循环来获取数据的,如果在第一轮循环中,a线程获取地址里面的值被b线程修改了,那么a线程需要自旋,到下次循环才有可能机会执行,如图所示:
- 由于CAS操作属于乐观派,它总认为自己可以成功完成操作,当多个线程同时使用CAS操作一个变量时,只有一个会胜出,并成功更新,其余均会失败,但失败的线程并不会被挂起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作,这点从图中也可以看出来。基于这样的原理,CAS操作即使没有锁,同样知道其他线程对共享资源操作影响,并执行相应的处理措施。同时从这点也可以看出,由于无锁操作中没有锁的存在,因此不可能出现死锁的情况,也就是说无锁操作天生免疫死锁。
- CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题
- CAS也是通过Unsafe实现的,看下Unsafe下的三个方法:
public final native boolean compareAndSwapObject(Object paramObject1, long paramLong, Object paramObject2, Object paramObject3);
public final native boolean compareAndSwapInt(Object paramObject, long paramLong, int paramInt1, int paramInt2);
public final native boolean compareAndSwapLong(Object paramObject, long paramLong1, long paramLong2, long paramLong3);
- 对于
compareAndSwapInt
,我们通过java来展示这种思想:
public int i = 1;
public boolean compareAndSwapInt(int j)
{
if (i == 1)
{
i = j;
return true;
}
return false;
}
- 当然这段代码在并发下是肯定有问题的,可能线程1进行if判断后,就切换到线程2了,这时候改变了i,就会造成错误。
- 解决办法: 给compareAndSwapInt方法加 锁同步就行了,这样,compareAndSwapInt方法就变成了一个原子操作。CAS也是一样的道理,比较、交换也是一组原子操作,不会被外部打断,先根据paramLong/paramLong1获取到内存当中当前的内存值V,在将内存值V和原值A作比较,要是相等就修改为要修改的值B,由于CAS都是硬件级别的操作,因此效率会高一些。
- 由CAS分析AtomicInteger原理
- java.util.concurrent.atomic包下的原子操作类都是基于CAS实现的,下面拿
AtomicInteger
分析一下,首先是AtomicInteger
类变量的定义: - 关于这段代码中出现的几个成员属性:
- Unsafe是CAS的核心类
- VALUE表示的是变量值在内存中的偏移地址,因为 Unsafe(U) 就是根据内存偏移地址获取数据的原值的
- value是用volatile修饰的,这是非常关键的
- 这时候我们差不多明白
AtomicInteger
的底层是和(UnSafe类—>Nativef方法)有关,要求更新的值用volatile 保存。 - 分析一个方法:
- java.util.concurrent.atomic包下的原子操作类都是基于CAS实现的,下面拿
public final int addAndGet(int delta) {
for (;;) {
int current = get();
int next = current + delta;
if (compareAndSet(current, next))
return next;
}
}
public final int get() {
return value;
}
- 这段代码如何在不加锁的情况下通过CAS实现线程安全,我们不妨考虑一下方法的执行:
- AtomicInteger里面的value原始值为3,即主内存中AtomicInteger的value为3,根据Java内存模型,线程1和线程2各自持有一份value的副本,值为3
- 线程1运行到第三行获取到当前的value为3,线程切换
- 线程2开始运行,获取到value为3,利用CAS对比内存中的值也为3,比较成功,修改内存,此时内存中的value改变比方说是4,线程切换
- 线程1恢复运行,利用CAS比较发现自己的value为3,内存中的value为4,得到一个重要的结论–>此时value正在被另外一个线程修改,所以我不能去修改它
- 线程1的compareAndSet失败,循环判断,因为value是volatile修饰的,所以它具备可见性的特性,线程2对于value的改变能被线程1看到,只要线程1发现当前获取的value是4,内存中的value也是4,说明线程2对于value的修改已经完毕并且线程1可以尝试去修改它
- 最后说一点,比如说此时线程3也准备修改value了,没关系,因为比较-交换是一个原子操作不可被打断,线程3修改了value,线程1进行compareAndSet的时候必然返回的false,这样线程1会继续循环去获取最新的value并进行compareAndSet,直至获取的value和内存中的value一致为止
- 整个过程中,利用CAS机制保证了对于value的修改的线程安全性。
- CAS的缺点
- "ABA"问题:
- CAS这种操作显然无法涵盖并发下的所有场景,并且CAS从语义上来说也不是完美的,存在这样一个逻辑漏洞:如果一个变量V初次读取的时候是A值,并且在准备赋值的时候检查到它仍然是A值,那我们就能说明它的值没有被其他线程修改过了吗?如果在这段期间它的值曾经被改成了B,然后又改回A,那CAS操作就会误认为它从来没有被修改过。这个漏洞称为CAS操作的"ABA"问题。
- CPU利用率问题: CAS造成CPU利用率增加。之前说过了CAS里面是一个循环判断的过程,如果线程一直没有获取到状态,cpu资源会一直被占用。
- "ABA问题"的解决方法:
- java.util.concurrent包为了解决这个问题,提供了一个带有标记的原子引用类"AtomicStampedReference",它可以 通过控制变量值的版本来保证CAS的正确性。不过目前来说这个类比较"鸡肋",大部分情况下ABA问题并不会影响程序并发的正确性,如果需要解决ABA问题,使用 传统的互斥同步 可能会避原子类更加高效。
- "ABA"问题:
JUC中的原子操作类:
- 从JDK 1.5开始提供了java.util.concurrent.atomic包,在该包中提供了许多基于CAS实现的原子操作类,用法方便,性能高效,主要分以下4种类型:
- 基本类型: AtomicInteger, AtomicLong, AtomicBoolean ;
- 数组类型: AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray ;
- 引用类型: AtomicReference, AtomicStampedRerence , AtomicMarkableReference ;
- 对象的属性修改类型: AtomicIntegerFieldUpdater, AtomicLongFieldUpdater, AtomicReferenceFieldUpdater 。
- 这些类存在的目的是对相应的数据进行原子操作。所谓原子操作,是指操作过程不会被中断,保证数据操作是以原子方式进行的。
原子更新基本类型:AtomicInteger , AtomicLong 和 AtomicBoolean 这3个基本类型的原子类的原理和用法相似。本节以AtomicLong对基本类型的原子类进行介绍
- AtomicLong介绍和函数列表
- AtomicLong作用是对长整形进行原子操作。
- 在32位操作系统中,64位的long 和 double 变量由于会被JVM当作两个分离的32位来进行操作,所以不具有原子性。而使用AtomicLong能让long的操作保持原子型。
// 构造AtomicLong函数
AtomicLong()
// 创建值为initialValue的AtomicLong对象
AtomicLong(long initialValue)
// 以原子方式设置当前值为newValue。
final void set(long newValue)
// 获取当前值
final long get()
// 以原子方式将当前值减 1,并返回减1后的值。等价于“--num”
final long decrementAndGet()
// 以原子方式将当前值减 1,并返回减1前的值。等价于“num--”
final long getAndDecrement()
// 以原子方式将当前值加 1,并返回加1后的值。等价于“++num”
final long incrementAndGet()
// 以原子方式将当前值加 1,并返回加1前的值。等价于“num++”
final long getAndIncrement()
// 以原子方式将delta与当前值相加,并返回相加后的值。
final long addAndGet(long delta)
// 以原子方式将delta添加到当前值,并返回相加前的值。
final long getAndAdd(long delta)
// 如果当前值 == expect,则以原子方式将该值设置为update。成功返回true,否则返回false,并且不修改原值。
final boolean compareAndSet(long expect, long update)
// 以原子方式设置当前值为newValue,并返回旧值。
final long getAndSet(long newValue)
// 返回当前值对应的int值
int intValue()
// 获取当前值对应的long值
long longValue()
// 以 float 形式返回当前值
float floatValue()
// 以 double 形式返回当前值
double doubleValue()
// 最后设置为给定值。延时设置变量值,这个等价于set()方法,但是由于字段是volatile类型的,因此次字段的修改会比普通字段(非volatile字段)有稍微的性能延时(尽管可以忽略),所以如果不是想立即读取设置的新值,允许在“后台”修改值,那么此方法就很有用。如果还是难以理解,这里就类似于启动一个后台线程如执行修改新值的任务,原线程就不等待修改结果立即返回(这种解释其实是不正确的,但是可以这么理解)。
final void lazySet(long newValue)
// 如果当前值 == 预期值,则以原子方式将该设置为给定的更新值。JSR规范中说:以原子方式读取和有条件地写入变量但不 创建任何 happen-before 排序,因此不提供与除 weakCompareAndSet 目标外任何变量以前或后续读取或写入操作有关的任何保证。大意就是说调用weakCompareAndSet时并不能保证不存在happen-before的发生(也就是可能存在指令重排序导致此操作失败)。但是从Java源码来看,其实此方法并没有实现JSR规范的要求,最后效果和compareAndSet是等效的,都调用了unsafe.compareAndSwapInt()完成操作。
final boolean weakCompareAndSet(long expect, long update)
- 因为AtomicInteger、AtomicIong的源码不多,我们直接看AtomicInteger源码
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
/*
* This class intended to be implemented using VarHandles(句柄), but there
* are unresolved cyclic startup dependencies.
* 注意!!!这个类打算使用VarHandles实现,但是存在无法解析的循环启动依赖项。
*/
// 获取指针类Unsafe
private static final Unsafe U = Unsafe.getUnsafe();
//下述变量value在AtomicInteger实例对象内的内存偏移量,记为VALUE,也就是早期版本的valueoffset
private static final long VALUE
= U.objectFieldOffset(AtomicInteger.class, "value");
//当前AtomicInteger封装的int变量value
private volatile int value;
public AtomicInteger(int initialValue) { //含参构造器
value = initialValue;
}
public AtomicInteger() {//空构造器
}
//获取当前最新值,因为value 用volatile修饰
public final int get() {
return value;
}
//设置当前值,具备volatile效果,方法用final修饰是为了更进一步的保证线程安全。
public final void set(int newValue) {
value = newValue;
}
//最终会设置成newValue,使用该方法后可能导致其他线程在之后的一小段时间内可以获取到旧值,有点类似于延迟加载
public final void lazySet(int newValue) {
U.putIntRelease(this, VALUE, newValue);
}
//获取旧值并设置新值,底层调用的是CAS操作即unsafe.compareAndSwapInt()方法
public final int getAndSet(int newValue) {
return U.getAndSetInt(this, VALUE, newValue);
}
//如果当前值为expect,则设置为update(当前值指的是value变量)
public final boolean compareAndSet(int expectedValue, int newValue) {
return U.compareAndSetInt(this, VALUE, expectedValue, newValue);
}
//下面的所有操作的底层是weakCompareAndSetPlain然后再底层才是CAS操作,下面有源码图
@Deprecated(since="9")
public final boolean weakCompareAndSet(int expectedValue, int newValue) {
return U.weakCompareAndSetIntPlain(this, VALUE, expectedValue, newValue);
}
/**
* Possibly atomically sets the value to {@code newValue}
* if the current value {@code == expectedValue},
* with memory effects as specified by {@link VarHandle#weakCompareAndSetPlain}.
*
* @param expectedValue the expected value 期望值
* @param newValue the new value 新值
* @return {@code true} if successful
* @since 9
*/
//下面的所有操作的底层是weakCompareAndSetPlain然后再底层才是CAS操作,下面有源码图
public final boolean weakCompareAndSetPlain(int expectedValue, int newValue) {
return U.weakCompareAndSetIntPlain(this, VALUE, expectedValue, newValue);
}
// 返回旧值并当前值加1,底层CAS --》compareAndSetInt操作
public final int getAndIncrement() {
return U.getAndAddInt(this, VALUE, 1);
}
// 返回旧值并当前值减1(+(-1)),底层CAS --》compareAndSetInt操作
public final int getAndDecrement() {
return U.getAndAddInt(this, VALUE, -1);
}
//当前值增加delta,返回旧值,底层CAS操作--》compareAndSetInt操作
public final int getAndAdd(int delta) {
return U.getAndAddInt(this, VALUE, delta);
}
//当前值加1,返回新值,底层CAS操作--》compareAndSetInt操作
public final int incrementAndGet() {
return U.getAndAddInt(this, VALUE, 1) + 1;
}
//当前值减1,返回新值,底层CAS操作--》compareAndSetInt操作
public final int decrementAndGet() {
return U.getAndAddInt(this, VALUE, -1) - 1;
}
//当前值增加delta,返回新值,底层CAS操作--》compareAndSetInt操作
public final int addAndGet(int delta) {
return U.getAndAddInt(this, VALUE, delta) + delta;
}
//省略一些不常用的方法....
- 通过上述的分析,可以发现AtomicInteger原子类的内部几乎是基于前面分析过Unsafe类中的CAS相关操作的方法实现的,这也证明了AtomicInteger是基于无锁实现的,这里重点分析自增(Long类型)操作实现过程,其他方法自增实现原理一样:
- AtomicLong的代码很简单,下面仅以incrementAndGet() 为例,对AtomicLong的原理进行说明。
- 我们发现AtomicIong类中所有自增或自减的方法都间接调用Unsafe类中的getAndAddLong()方法实现了CAS操作,从而保证了线程安全,关于getAndAddLong()其实前面已分析过,它是Unsafe类中1.8新增的方法,源码如下:
- 可看出getAndAddLong()通过一个do-while循环不断的重试更新要设置的值,直到成功为止,调用的是Unsafe类中的weakcompareAndSwapLong()(其中包含了compareAndSwapLong()方法)方法,是一个CAS操作方法。
- incrementAndGet()源码如下:
- 说明:
- incrementAndGet()内部调用的getAndLong()方法。
- incrementAndGet()内部通过offset和volatile获取内存中的最新值,然后执行第4,5步,最后就是compareAndSetLong()这个CAS方法
- compareAndSetLong()的作用是更新AtomicLong对应的long值。它会通过偏移量取内存中相应位置的值,看是否与expected相等,若相等的话,则设置AtomicLong的值为x。
- 通俗来说:我们通过volatile(native()方法)来取内存中的值,然后通过CAS操作再将通过volatile取出来的值和内存中的值比较,如果相等,赋成新的值。
- 这里需要注意的是,上述源码分析是基于JDK1.8的,如果是1.8之前的方法,Atomiclong源码实现有所不同,是基于for死循环的,如下:
- 说明:
//JDK 1.7的源码,由for的死循环实现,并且直接在AtomicLong中实现该方法,
//JDK1.8后,该方法实现已移动到Unsafe类中,直接调用getAndAddLong方法即可
public final long incrementAndGet() {
for (;;) {
// 获取AtomicLong当前对应的long值
long current = get();
// 将current加1
long next = current + 1;
// 通过CAS函数,更新current的值
if (compareAndSet(current, next))
return next;
}
}
- 结果
package com.wwj.text;
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicLongTest {
static AtomicInteger count = new AtomicInteger(0);
static class AddCount implements Runnable{
@Override
public void run() {
for(int i=0 ; i<1000 ; i++){
count.incrementAndGet();
}
}
}
public static void main(String[] args) throws InterruptedException{
Thread[] ts = new Thread[10];
for(int i=0 ; i<10 ; i++){
ts[i] = new Thread(new AddCount());
}
for(int i=0 ; i<10 ; i++){
ts[i].start();
}
for(int i=0 ; i<10 ; i++){
ts[i].join();
}
System.out.println(count);
}
}
/
//结果:10000
原子更新数组:AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray这3个数组类型的原子类的原理和用法相似。本章以AtomicLongArray对数组类型的原子类进行介绍。内容包括:
- AtomicLong是作用是对长整形进行原子操作。而AtomicLongArray的作用则是对"长整形数组"进行原子操作。
- AtomicLongArray函数列表(JDK9以前的):
// 创建给定长度的新 AtomicLongArray。
AtomicLongArray(int length)
// 创建与给定数组具有相同长度的新 AtomicLongArray,并从给定数组复制其所有元素。
AtomicLongArray(long[] array)
// 以原子方式将给定值添加到索引 i 的元素。
long addAndGet(int i, long delta)
// 如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。
boolean compareAndSet(int i, long expect, long update)
// 以原子方式将索引 i 的元素减1。
long decrementAndGet(int i)
// 获取位置 i 的当前值。
long get(int i)
// 以原子方式将给定值与索引 i 的元素相加。
long getAndAdd(int i, long delta)
// 以原子方式将索引 i 的元素减 1。
long getAndDecrement(int i)
// 以原子方式将索引 i 的元素加 1。
long getAndIncrement(int i)
// 以原子方式将位置 i 的元素设置为给定值,并返回旧值。
long getAndSet(int i, long newValue)
// 以原子方式将索引 i 的元素加1。
long incrementAndGet(int i)
// 最终将位置 i 的元素设置为给定值。
void lazySet(int i, long newValue)
// 返回该数组的长度。
int length()
// 将位置 i 的元素设置为给定值。
void set(int i, long newValue)
// 返回数组当前值的字符串表示形式。
String toString()
// 如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。
boolean weakCompareAndSet(int i, long expect, long update)
- 代码示例:
package com.wwj.text;
import java.util.concurrent.atomic.AtomicIntegerArray;
public class AtomicIntegerDemo {
static AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(10);
public static class AddThread implements Runnable{
@Override
public void run() {
for(int i=0 ; i<10000 ; i++){
atomicIntegerArray.incrementAndGet(i%atomicIntegerArray.length());
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread[] thread = new Thread[7];
for(int i=0 ; i<7; i++){
thread[i] = new Thread(new AddThread());
}
for(int i=0 ; i<7 ; i++){
thread[i].start();
}
for(int i=0 ; i<7 ; i++){
thread[i].join();
}
System.out.println(atomicIntegerArray);
}
}
//jieguo:[7000, 7000, 7000, 7000, 7000, 7000, 7000, 7000, 7000, 7000]
- 分析: 启动7条线程对数组中的元素进行自增操作,执行结果符合预期(一共加了70000次,平均到10个数组就是7000)。
- AtomicLongArray jdk9以前源码:
- AtomicLongArray jdk9以后源码:
- 通过源码可以看出,JDK9之后很多都是用varHandle替代unsafe,最底层还是unsafe
原子更新引用类型: 可以同时更新引用类型,这里主要分析一下AtomicReference原子类,即原子更新引用类型。
- 代码示例:
import java.util.concurrent.atomic.AtomicReference;
public class AtomicReferenceDemo {
public static AtomicReference<User> atomicReference = new AtomicReference<User>();
public static void main(String[] args) {
User user = new User("wwj",18);
atomicReference.set(user);
User updateUser = new User("md", 15);
atomicReference.compareAndSet(user,updateUser);
System.out.println(atomicReference.get().toString());
}
static class User{
public String name;
private int age;
public User(String name , int age){
this.name = name;
this.age = age;
}
public String getName(){
return name;
}
public String toString(){
return "User{"+"name="+name+", age="+ age+"}";
}
}
}
//结果:User{name=md, age=15}
- AtomicReference原子类内部是如何实现CAS操作的呢?:
public class AtomicReference<V> implements java.io.Serializable {
private static final long serialVersionUID = -1848883965231344442L;
private static final VarHandle VALUE;
static {
try {
MethodHandles.Lookup l = MethodHandles.lookup();
VALUE = l.findVarHandle(AtomicReference.class, "value", Object.class);
} catch (ReflectiveOperationException e) {
throw new ExceptionInInitializerError(e);
}
}
//内部变量value,Unsafe类通VALUE内存偏移量即可获取该变量
private volatile V value;
//CAS方法,间接调用compareAndSet,它是一个
//实现了CAS操作的native方法,但是好像他不是Unsafe的,是varHandle的 package java.lang.invoke;
//varHandle是jdk9开始的,作用之一是用来解决unsafe的缺点,用来替代部分的unsafe的使用场景
public final boolean compareAndSet(V expectedValue, V newValue) {
return VALUE.compareAndSet(this, expectedValue, newValue);
}
public final native
@MethodHandle.PolymorphicSignature
@HotSpotIntrinsicCandidate
boolean compareAndSet(Object... args);
//同上,所以本质都是CAS方法
public final V getAndSet(V newValue) {
return (V)VALUE.getAndSet(this, newValue);
}
public final native
@MethodHandle.PolymorphicSignature
@HotSpotIntrinsicCandidate
Object getAndSet(Object... args);
- 它是通过 “volatile” 和 "Unsafe提供的CAS函数 实现"原子操作:
- value是volatile类型。这保证了:当某线程修改value的值时,其他线程看到的value值都是最新的value值,即修改之后的volatile的值。
- 通过CAS设置value。这保证了:当某线程池通过CAS函数(如compareAndSet函数)设置value时,它的操作是原子的,即线程在操作value时不会被中断。
原子更新属性:
- 如果我们只需要某个类里的某个字段,也就是说让普通的变量也享受原子操作,可以使用原子更新字段类,如在某些时候由于项目前期考虑不周全,项目需求又发生变化,使得某个类中的变量需要执行多线程操作,由于该变量多处使用,改动起来比较麻烦,而且原来使用的地方无需使用线程安全,只要求新场景需要使用时,可以借助原子更新器处理这种场景,Atomic并发包提供了以下三个类:
- AtomicIntegerFieldUpdater:原子更新整型的字段的更新器
- AtomicLongFieldUpdater:原子更新长整型字段的更新器
- AtomicReferenceFieldUpdater:原子更新引用类型里的字段
- 请注意原子更新器的使用存在比较苛刻的条件如下:
- 操作的字段 不能是static 类型。
- 操作的字段 不能是final类型 的,因为final根本没法修改。
- 字段 必须是volatile修饰 的,也就是数据本身是读一致的。
- 属性必须对当前的Updater所在的区域是可见的,如果不是当前类内部进行原子更新器操作不能使用private,protected子类操作父类时修饰符必须是 protect权限及以上 ,如果在同一个package下则必须是default权限及以上,也就是说无论何时都应该保证操作类与被操作类间的可见性。
- AtomicLongFieldUpdater函数列表
// 受保护的无操作构造方法,供子类使用。
protected AtomicLongFieldUpdater()
// 以原子方式将给定值添加到此更新器管理的给定对象的字段的当前值。
long addAndGet(T obj, long delta)
// 如果当前值 == 预期值,则以原子方式将此更新器所管理的给定对象的字段设置为给定的更新值。
abstract boolean compareAndSet(T obj, long expect, long update)
// 以原子方式将此更新器管理的给定对象字段当前值减 1。
long decrementAndGet(T obj)
// 获取此更新器管理的在给定对象的字段中保持的当前值。
abstract long get(T obj)
// 以原子方式将给定值添加到此更新器管理的给定对象的字段的当前值。
long getAndAdd(T obj, long delta)
// 以原子方式将此更新器管理的给定对象字段当前值减 1。
long getAndDecrement(T obj)
// 以原子方式将此更新器管理的给定对象字段的当前值加 1。
long getAndIncrement(T obj)
// 将此更新器管理的给定对象的字段以原子方式设置为给定值,并返回旧值。
long getAndSet(T obj, long newValue)
// 以原子方式将此更新器管理的给定对象字段当前值加 1。
long incrementAndGet(T obj)
// 最后将此更新器管理的给定对象的字段设置为给定更新值。
abstract void lazySet(T obj, long newValue)
// 为对象创建并返回一个具有给定字段的更新器。
static <U> AtomicLongFieldUpdater<U> newUpdater(Class<U> tclass, String fieldName)
// 将此更新器管理的给定对象的字段设置为给定更新值。
abstract void set(T obj, long newValue)
// 如果当前值 == 预期值,则以原子方式将此更新器所管理的给定对象的字段设置为给定的更新值。
abstract boolean weakCompareAndSet(T obj, long expect, long update)
- 简单了解一下AtomicIntegerFieldUpdater的实现原理,实际就是 反射和Unsafe类结合
- AtomicIntegerFieldUpdater的源码:
@CallerSensitive
public static <U> AtomicIntegerFieldUpdater<U> newUpdater(Class<U> tclass,
String fieldName) {
return new AtomicIntegerFieldUpdaterImpl<U>
(tclass, fieldName, Reflection.getCallerClass());
}
- 我们发现它实际调用的是AtomicIntegerFieldUpdaterImpl< U >
- AtomicIntegerFieldUpdaterImpl< U >的源码:
private static final class AtomicIntegerFieldUpdaterImpl<T>
extends AtomicIntegerFieldUpdater<T> {
//通过unsafe类实现
private static final Unsafe U = Unsafe.getUnsafe();
//内存偏移量
private final long offset;
/**
* if field is protected, the subclass constructing updater, else
* the same as tclass
*/
private final Class<?> cclass;
/** class holding the field */
private final Class<T> tclass;
AtomicIntegerFieldUpdaterImpl(final Class<T> tclass,
final String fieldName,
final Class<?> caller) {
final Field field;
final int modifiers;
try {
field = AccessController.doPrivileged(
new PrivilegedExceptionAction<Field>() {
public Field run() throws NoSuchFieldException {
//反射获取字段对象
return tclass.getDeclaredField(fieldName);
}
});
//获取字段修饰符
modifiers = field.getModifiers();
//对字段的访问权限进行检查,不在访问范围内抛异常
sun.reflect.misc.ReflectUtil.ensureMemberAccess(
caller, tclass, null, modifiers);
ClassLoader cl = tclass.getClassLoader();
ClassLoader ccl = caller.getClassLoader();
if ((ccl != null) && (ccl != cl) &&
((cl == null) || !isAncestor(cl, ccl))) {
sun.reflect.misc.ReflectUtil.checkPackageAccess(tclass);
}
} catch (PrivilegedActionException pae) {
throw new RuntimeException(pae.getException());
} catch (Exception ex) {
throw new RuntimeException(ex);
}
if (field.getType() != int.class)
//判断是否为int类型
throw new IllegalArgumentException("Must be integer type");
if (!Modifier.isVolatile(modifiers))
//判断是否被volatile修饰
throw new IllegalArgumentException("Must be volatile type");
// Access to protected field members is restricted to receivers only
// of the accessing class, or one of its subclasses, and the
// accessing class must in turn be a subclass (or package sibling)
// of the protected member's defining class.
// If the updater refers to a protected field of a declaring class
// outside the current package, the receiver argument will be
// narrowed to the type of the accessing class.
this.cclass = (Modifier.isProtected(modifiers) &&
tclass.isAssignableFrom(caller) &&
!isSamePackage(tclass, caller))
? caller : tclass;
this.tclass = tclass;
//获取该字段的在对象内存的偏移量,通过内存偏移量可以获取或者修改该字段的值
this.offset = U.objectFieldOffset(field);
}
- 我们通过AtomicIntegerFieldUpdaterImpl< T > 的构造器可以发现限制条件为什么这么多了,我们可以在其中 发现是通过unsafe完成的
- 看其中的几个方法:
public final int incrementAndGet(T obj) {
return getAndAdd(obj, 1) + 1;
}
public final int getAndAdd(T obj, int delta) {
accessCheck(obj);
return U.getAndAddInt(obj, offset, delta);
}
@HotSpotIntrinsicCandidate
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!weakCompareAndSetInt(o, offset, v, v + delta));
return v;
}
@HotSpotIntrinsicCandidate
public final boolean weakCompareAndSetInt(Object o, long offset,
int expected,
int x) {
return compareAndSetInt(o, offset, expected, x);
}
@HotSpotIntrinsicCandidate
public final native boolean compareAndSetInt(Object o, long offset,
int expected,
int x);
- 我们可以发现,最终调用的还是unsafe.compareAndSetInt()方法
- 代码示例:
package com.wwj.text;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
public class AtomicIntegerFieldUpder {
public static class Candidate{
int id;
volatile int score; //必须volatile修饰
}
public static class Game{
int id ;
volatile String name; //必须volatile修饰
public Game(int id , String name){
this.id = id;
this.name = name;
}
public String toString(){
return "Game{"+"id="+id+", name=" + name + "}";
}
}
static AtomicIntegerFieldUpdater<Candidate> atIntegerUpdater = AtomicIntegerFieldUpdater.newUpdater(Candidate.class, "score");
static AtomicReferenceFieldUpdater<Game,String> atRefUpdate = AtomicReferenceFieldUpdater.newUpdater(Game.class,String.class,"name");
//用于验证分数是否正确
public static AtomicInteger allScore=new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException{
final Candidate stu=new Candidate();
Thread[] t=new Thread[10000];
//开启10000个线程
for(int i = 0 ; i < 10000 ; i++) {
t[i]=new Thread() {
public void run() {
if(Math.random()>0.4){
atIntegerUpdater.incrementAndGet(stu);
allScore.incrementAndGet();
}
}
};
t[i].start();
}
for(int i = 0 ; i < 10000 ; i++) {
t[i].join();
}
System.out.println("最终分数score="+stu.score);
System.out.println("校验分数allScore="+allScore);
//AtomicReferenceFieldUpdater 简单的使用
Game game = new Game(2,"wwj");
atRefUpdate.compareAndSet(game,game.name,"CTO-WWJ");
System.out.println(game.toString());
}
}
- 结果:
最终分数score=5980
校验分数allScore=5980
Game{id=2, name=CTO-WWJ}
- 结果分析:
- 我们使用AtomicIntegerFieldUpdater更新候选人(Candidate)的分数score,开启了10000条线程投票,当随机值大于0.4时算一票,分数自增一次,其中allScore用于验证分数是否正确(其实用于验证AtomicIntegerFieldUpdater更新的字段是否线程安全),当allScore与score相同时,则说明投票结果无误,也代表AtomicIntegerFieldUpdater能正确更新字段score的值,是线程安全的。
- 对于AtomicReferenceFieldUpdater,我们在代码中简单演示了其使用方式,注意在AtomicReferenceFieldUpdater 注明泛型时需要两个泛型参数,一个是修改的类类型,一个修改字段的类型。 至于AtomicLongFieldUpdater则与AtomicIntegerFieldUpdater 类似,不再介绍。
CAS的"ABA"问题及其解决方案
- 我们再次阐述什么是ABA问题:如图
- 什么意思呢?就是说一个线程把数据A变为了B,然后又重新变成了A。此时另外一个线程读取的时候,发现A没有变化,就误以为是原来的那个A。这就是典型的CAS的ABA问题。
- 一般情况下这种情况发现的概率比较小,可能发生了也不会造成什么问题,但是可能也会造成了严重的后果,我们举几个例子:
- 我们对某个做加减法,不关心数字的过程,那么发生ABA问题也没啥关系。
- 一个小偷,把别人家的钱偷了之后又还了回来,还是原来的钱吗,你老婆出轨之后又回来,还是原来的老婆吗? ABA问题也一样,如果不好好解决就会带来大量的问题。最常见的就是资金问题,也就是别人如果挪用了你的钱,在你发现之前又还了回来。但是别人却已经触犯了法律。
- 那我们需要解决此类 “ABA” 问题,怎么解决的?我们可以使用以下两个原子类:
- AtomicStampedReference类
- AtomicMarkableReference类
- AtomicStampedReference
- AtomicStampedReference原子类是一个带有时间戳的对象引用;
- 在每次修改后,AtomicStampedReference 不仅会设置新值而且还会记录更改的时间;
- 当AtomicStampedReference设置对象值时,对象值以及时间戳都必须满足期望值才能写入成功,这也就解决了反复读写时,无法预知值是否已被修改的窘境。
- AtomicStampedReference内部源码分析:
public class AtomicStampedReference<V> {
//通过Pair内部类 存储数据和时间戳
private static class Pair<T> {
final T reference;
final int stamp;
private Pair(T reference, int stamp) {
this.reference = reference;
this.stamp = stamp;
}
static <T> Pair<T> of(T reference, int stamp) {
return new Pair<T>(reference, stamp);
}
}
//存储数值和时间的内部类,用volatile修饰
private volatile Pair<V> pair;
//构造器,创建时需传入初始值和时间初始值
public AtomicStampedReference(V initialRef, int initialStamp) {
pair = Pair.of(initialRef, initialStamp);
}
- 我们可以发现AtomicStampedReference的实现,pair很关键:我们进一步其他方法:
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp) {
Pair<V> current = pair;
return
//当前数据的对比和当前时间的对比,只有都相等的时候才能执行casPair
expectedReference == current.reference &&
expectedStamp == current.stamp &&
((newReference == current.reference &&
newStamp == current.stamp) ||
casPair(current, Pair.of(newReference, newStamp)));
}
- 进一步看casPair:
private boolean casPair(Pair<V> cmp, Pair<V> val) {
return PAIR.compareAndSet(this, cmp, val);
}
- 我们发现是通过PAIR调用的compareAndSet,在之前的jdk中是UNSAFE调用,点开发现本质是varHandle类的compareAndSet,取代了UNSAFE
public final native
@MethodHandle.PolymorphicSignature
@HotSpotIntrinsicCandidate
boolean compareAndSet(Object... args);
- 梳理下AtomicStampedReference的内部实现思路:
- 通过一个键值对Pair存储数据和时间戳;
- 在更新时对数据和时间戳进行比较;
- 只有两者都符合预期才会调用varHandle的compareAndSet方法执行数值和时间戳替换;
- 这样就避免了ABA的问题。
- compareAndSet实例运用:
- 通过AtomicInteger和AtomicStampedReference进行比较
- 先看AtomicInteger造成的ABA问题:
package com.wwj.text;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;
public class ABADemo {
//方式1:
static AtomicInteger atomicInteger = new AtomicInteger(50);
static Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
//通过 atomicInteger 更新为100
atomicInteger.compareAndSet(50,100);
//通过 atomicInteger 再更新为50
atomicInteger.compareAndSet(100,50);
}
});
static Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
//略微停顿一下,使另一个线程执行完
try{
TimeUnit.SECONDS.sleep(1);
}catch (InterruptedException e){
e.printStackTrace();
}
boolean flag = atomicInteger.compareAndSet(50,120);
System.out.println("flag:"+flag+",atomicInteger:"+atomicInteger);
}
});
public static void main(String[] args) throws InterruptedException{
t1.start();
t2.start();
}
}
//结果:flag:true,atomicInteger:120
- 通过AtomicInteger的结果可以发现:我们没有发现atomicInteger 有变化,所以造成了ABA问题,虽然执行代码没有什么严重的影响,但是放在别的场景可能就不一样了
- 再看AtomicInteger如何解决ABA问题:
package com.wwj.text;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;
public class ABADemo {
//方式1:
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(50,0);
static Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
//通过 AtomicStampedReference 更新为100
int time = atomicStampedReference.getStamp();
System.out.println("t3线程一开始的时间戳/stamp---->"+atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(50,100,time,time+1);
System.out.println("t3线程reference从50->100改变后相应时间戳/stamp---->"+atomicStampedReference.getStamp());
//通过 AtomicStampedReference 再更新为50
int time1 = atomicStampedReference.getStamp();
atomicStampedReference.compareAndSet(100,50,time1,time1+1);
System.out.println("t3线程reference从100->50改变后相应时间戳/stamp---->"+atomicStampedReference.getStamp());
}
});
static Thread t4 = new Thread(new Runnable() {
@Override
public void run() {
int time = atomicStampedReference.getStamp();
//休息一秒,是让t3执行完
System.out.println("t4线程一开始的时间戳/stamp---->"+atomicStampedReference.getStamp());
try{
TimeUnit.SECONDS.sleep(1);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("t4线程reference从50->120改变前相应时间戳/stamp---->"+atomicStampedReference.getStamp());
boolean flag = atomicStampedReference.compareAndSet(50,120,time,time+1);
System.out.println("flag:"+flag+",atomicStampedReference:"+atomicStampedReference.getReference());
}
});
public static void main(String[] args) throws InterruptedException{
t3.start();
t4.start();
}
}
- 相应结果分析:
- AtomicMarkableReference
- AtomicMarkableReference与AtomicStampedReference不同的是,AtomicMarkableReference 维护的是一个boolean值的标识,也就是说至于true和false两种切换状态
- 这种方法不能完全"杜绝ABA"问题,只能减少ABA发生的次数。
- CAS的应用-------->自旋锁
- 转至:点击进入!!!!
- 何谓自旋锁? 它是为实现保护共享资源而提出一种锁机制。其实,自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。
- 在经过若干次循环后,如果得到锁,就顺利进入临界区。如果还不能获得锁,那就会将线程在操作系统层面挂起,这种方式确实也是可以提升效率的。但问题是当线程竞争越来越激烈时,占用CPU的时间变长会导致性能急剧下降,因此JVM内部一般对于自旋锁有一定的次数限制,可能是50或者100次循环后就放弃,直接挂起线程,让出CPU资源。
- 如下通过AtomicReference可实现简单的自旋锁。
package com.wwj.text;
import java.util.concurrent.atomic.AtomicReference;
public class SpinLock {
AtomicReference<Thread> sign = new AtomicReference();
public void luck(){
Thread currentThread = Thread.currentThread();
while(!sign.compareAndSet(null,currentThread));
}
public void unLuck(){
Thread currentThread = Thread.currentThread();
while(!sign.compareAndSet(currentThread,null));
}
}
- 解析:
- 使用CAS原子操作作为底层实现;
- lock()方法将要更新的值设置为当前线程,并将预期值设置为null。
- unlock()函数将要更新的值设置为null,并预期值设置为当前线程。
- 然后我们通过lock()和unlock来控制自旋锁的开启与关闭,注意这是一种非公平锁。事实上AtomicInteger(或者AtomicLong)原子类内部的CAS操作也是通过不断的自循环(while循环)实现,不过这种循环的结束条件是线程成功更新对于的值,但也是自旋锁的一种。
- 番外话:
- JDK9以后,引入了句柄类varHandle,替代了很多Unsafe类的操作,具体为什么替代哪些替代以后再总结。【这里是var Handle的文章】
更多推荐
已为社区贡献6条内容
所有评论(0)