Unsafe包
Unsafe包提供一些直接操作底层资源的方法,这个包是不安全的
AQS底层依赖的CAS操作就是Unsafe包提供的
CAS是Compare And Swap的缩写,直译就是比较并交换。CAS是现代CPU广泛支持的一种对内存中的共享数据进行操作的一种特殊指令,这个指令会对内存中的共享数据做原子的读写操作。
本质上来讲CAS是一种无锁的解决方案,也是一种基于乐观锁的操作,可以保证在多线程并发中保障共享资源的原子性操作,相对于synchronized或Lock来说,是一种轻量级的实现方案。
Unsafe包对CAS操作的实现方式是:我认为原有的值应该是什么,如果是,则将原有的值更新为新值,否则不做修改,并告诉我原来的值是多少
compareAndSetXX方法
Unsafe提供的CAS操作比较常见的包括:
// 引用类型
compareAndSetObject; // 不支持,改用reference
compareAndSetReference;
// 八种基本数据类型
compareAndSetInt;
compareAndSetLong;
compareAndSetByte;
compareAndSetChar;
compareAndSetShort;
compareAndSetBoolean;
compareAndSetFloat;
compareAndSetDouble;
……其中:
基本类型compareAndSetInt、compareAndSetLong,引用类型compareAndSetReference是native方法
compareAndSetBoolean基于compareAndSetByte实现,二者底层调用compareAndExchangeByte
compareAndSetChar基于compareAndSetShort,二者底层调用compareAndExchangeShort
compareAndSetFloat基于compareAndSetInt,compareAndSetDouble基于compareAndSetLong
而compareAndExchangeByte和compareAndExchangeShort底层也是转成int处理
do {
fullWord = getIntVolatile(o, wordOffset);
if ((fullWord & mask) != maskedExpected) {
return (short) ((fullWord & mask) >> shift);
}
} while (!weakCompareAndSetInt(o, wordOffset, fullWord, (fullWord & ~mask) | maskedX));CAS操作常见实现就是Atomic系列和ConcurrentHashMap
getAndAddXX方法
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!weakCompareAndSetInt(o, offset, v, newValue));
return v;
}分析一个Unsafe包中的方法getAndAddInt
其中参数Object o代表要getAndAdd的对象,long offset代表内存地址,int delta代表自增步伐,通过native方法获取值,然后比较设置,如果失败,则执行无限的自旋,重新获取v的值
getIntVolatile和compareAndSetInt方法都是native方法,这个方法汇编之后是CPU原语指令,原语指令是连续执行不会被打断的,所以可以保证原子性
但在getAndAddInt方法中还涉及到一个实现自旋锁。所谓的自旋,其实就是上面getAndAddInt方法中的do while循环操作。当预期值和主内存中的值不等时,就重新获取主内存中的值,这就是自旋
CAS的缺点
CAS的一些缺点在于:
循环时间长,开销大;
只能保证一个共享变量的原子操作;
ABA问题;
循环是因为其自旋的操作。而第二个就显而易见了,CAS的设计初衷就是一个变量,而非一个代码块。
ABA问题指的是:两个线程读取一个变量i=A,t1读到i时,发现i已经被t2抢占,t2对i进行了操作,先改成B,又改回A,可能使t1认为i没有被改变过。
而CAS判定的是地址,如果在自旋过程中地址被销毁后又重新分配了,这个流程就有问题了。
对此JDK提供了AtomicStampedReference来解决ABA问题。
LockSupport包 - unsafe支持的线程操作
park
可以用于线程暂停,关注下几种暂停方法的差异:
Thread.sleep()
必须指定休眠时间
休眠后状态为TIMED_WAITING
需要捕获InterruptedException异常
不释放持有的锁
Object.wait()/Thread.wait()
必须在锁住对象的前提下才能操作,使用lock方法或sychronized语法
可以通过notify()唤醒,但如果notify发生在wait之前会丢信号
休眠时状态为WAITING
需要捕获InterruptedException异常
释放持有的锁
LockSupport.park()/parkNanos()
通过二元信号量实现阻塞
休眠时状态为WAITING或TIMED_WAITING
不需要捕获InterruptedException异常,但也会响应中断
不释放持有的锁
可以通过unpark唤醒,且unpark可以在park前执行,不丢失信号
VarHandle包 - jdk9对Unsafe的替代
从JDK9开始, 会尽可能使用VarHandle代替Unsafe,除了atomic包下一些依赖问题没解决,很多API都使用 VarHandle代替了。
VarHandle提供了一系列标准的内存屏障操作,用于更加细粒度的控制内存排序。在安全性、可用性、性能上都要优于现有的API。 VarHandle可以与任何字段、数组元素或静态变量关联,支持在不同访问模型下对这些类型变量的访问,包括简单的read/write访问, volatile类型的 read/write访问,和CAS(compare-and-swap)等。
Atomic包
Atomic包在java.util.concurrent.atomic中,提供了java基本类型、引用类型的原子操作能力,基于Unsafe包实现。
这套代码的目的是为了解决多线程并发操作变量的修改问题。
AtomicInteger、AtomicLong - 基本数据类型int、long的原子操作包
方法类似,以AtomicInteger为例,提供方法包括:
// 以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果
int addAndGet(int delta)
// 如果输入的数值等于预期值,则以原子方式将该值设置为输入的值
boolean compareAndSet(int expect,int update)
// 以原子方式将当前值加1,注意,这里返回的是自增前的值
int getAndIncrement()
// 最终会设置成newValue,使用lazySet设置值后,可能导致其他线程在之后的一小段时间内还是可以读到旧的值
void lazySet(int newValue)
// 以原子方式设置为newValue的值,并返回旧值
int getAndSet(int newValue)
核心成员变量
private static final Unsafe U = Unsafe.getUnsafe();维护了一个Unsafe的成员变量,因此Atomic底层是基于Unsafe实现的
getAndIncrement
return U.getAndAddInt(this, VALUE, 1);调用Unsafe#getAndAddInt方法
compareAndSet
return U.compareAndSetInt(this, VALUE, expectedValue, newValue);AtomicBoolean - 基本数据类型bool的原子操作
提供的方法和AtomicInt类似,但是已经不再使用Unsafe实现,而是改用了VarHandle
private static final VarHandle VALUE;
……
return VALUE.compareAndSet(this, (expectedValue ? 1 : 0), (newValue ? 1 : 0));AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
数组类型的原子操作提供如下方法:
// 以原子方式将输入值与数组中索引i的元素相加
int addAndGet(int i,int delta)
// 如果当前值等于预期值,则以原子方式将数组位置i的元素设置成update值
boolean compareAndSet(int i,int expect,int update)构造方法
public AtomicLongArray(long[] array) {
this.array = array.clone();
}这里需要注意:使用原始数组构造Atomic数组时,传进来的数组执行clone方法,构造了一个新数组,因此对原本数组不产生任何改变
getAndSet
private static final VarHandle AA = MethodHandles.arrayElementVarHandle(long[].class);
……
return (long)AA.getAndSet(array, i, newValue);也是通过VarHandle实现的,这里Intger、Long、Reference三种是一样的
AtomicReference、AtomicReferenceFieldUpdater、AtomicMarkableReference
AtomicReference:原子更新引用类型。
AtomicReferenceFieldUpdater:原子更新引用类型里的字段。
AtomicMarkableReference:原子更新带有标记位的引用类型。可以原子更新一个布尔类型的标记位和引用类型。构造方法是
AtomicMarkableReference(V initialRef,booleaninitialMark)
AtomicReference
构造函数
public AtomicReference(V initialValue) {
value = initialValue;
}虽然内部也是用的VarHandle,但是因为是引用持有给了,因此修改Atomic变量也会修改原始对象
AtomicReferenceFieldUpdater
实现类是AtomicReferenceFieldUpdaterImpl,内部是用的Unsafe
构造函数通过反射获取原始对象的每个字段
AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicStampedReference
AtomicIntegerFieldUpdater:原子更新整型的字段的更新器
AtomicLongFieldUpdater:原子更新长整型字段的更新器
AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于原子的更新数据和数据的版本号,可以解决使用CAS进行原子更新时可能出现的ABA问题
评论区