目 录CONTENT

文章目录

其他锁相关的包

FatFish1
2025-08-13 / 0 评论 / 0 点赞 / 25 阅读 / 0 字 / 正在检测是否收录...

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 - jdk9Unsafe的替代

从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

构造函数通过反射获取原始对象的每个字段

AtomicIntegerFieldUpdaterAtomicLongFieldUpdaterAtomicStampedReference

  • AtomicIntegerFieldUpdater:原子更新整型的字段的更新器

  • AtomicLongFieldUpdater:原子更新长整型字段的更新器

  • AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于原子的更新数据和数据的版本号,可以解决使用CAS进行原子更新时可能出现的ABA问题

0

评论区