JAVA并发编程

48 阅读4分钟

1、JUC工具包的基石 - UnSafe

UnSafe类,提供了一系列绕过 JVM 安全机制、直接操作内存和硬件的功能。

功能类别典型方法作用
内存操作allocateMemory/freeMemory/putInt/getObject直接分配/释放堆外内存,读写任意内存地址
CAS 原子操作compareAndSwapObject/compareAndSwapInt/compareAndSwapLong实现无锁算法(如 AtomicXXX 类的核心)
对象操作objectFieldOffset/getObjectVolatile/putOrderedObject直接获取对象字段偏移地址,绕过访问修饰符读写字段
线程调度park/unpark直接挂起/唤醒线程(LockSupport 的底层实现)
内存屏障loadFence/storeFence/fullFence控制内存可见性和指令重排序(volatile 的底层实现)
类操作defineClass/allocateInstance动态定义类或不执行构造器创建对象

1.1 CAS 操作示例

// Java 声明
public final native boolean compareAndSwapInt(
    Object o, long offset, int expected, int x
);

// Hotspot JVM 的 C++ 实现 (atomic.cpp)
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(...)) {
  oop p = JNIHandles::resolve(obj);
  jint* addr = (jint*)index_oop_from_field_offset_long(p, offset);
  return Atomic::cmpxchg(x, addr, expected) == expected; // 调用 CPU 原子指令
} UNSAFE_END
  • 底层硬件支持:最终映射到 CPU 的 CMPXCHG 指令(x86 架构)实现原子操作。

1.2 内存屏障实现

// Java 声明
public native void loadFence();

// Hotspot 实现 (unsafe.cpp)
UNSAFE_ENTRY(void, Unsafe_LoadFence()) {
  OrderAccess::acquire(); // 调用内存屏障指令
} UNSAFE_END
  • 硬件映射:在 x86 上编译为 lock addl $0x0,(%rsp) 指令,刷新 CPU 缓存。

1.3 实战案例:AtomicInteger 如何用 UNSAFE 实现

public class AtomicInteger {
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset; // 字段内存偏移量

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile int value;

    public final boolean compareAndSet(int expect, int update) {
        // 调用 Unsafe 的 CAS 原子操作
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
}

关键步骤

  • 启动时通过反射获取 value 字段的内存偏移地址
  • CAS 操作时直接通过内存地址修改值
  • volatile 保证修改的可见性

1.4 LockSupport

LockSupport为AQS和ReentrantLock提供了底层支持,同时LockSupport的底层实现原理是调用UNSAFE的park()和unpark()

public static void park() {
    UNSAFE.park(false, 0L); // 调用 Unsafe
}

public static void unpark(Thread thread) {
    if (thread != null) {
        UNSAFE.unpark(thread);
    }
}

先调用unpark再调用park,线程则不会阻塞,调用不会丢失,多次调用unpark只能累积一次

2、AbstractQueuedSynchronizer

公平性:

闯入策略是AQS框架为了提升性能而设计的一个策略,具体是指一个新线程到达共享资源边界时不管等待队列中是否存在其它等待节点,新线程都将优先尝试去获取锁,这看起来就像是闯入行为。闯入策略破坏了公平性,AQS框架对外体现的公平性主要也由此体现。

AQS提供的锁获取操作运用了可闯入算法,即如果有新线程到来先进行一次获取尝试,不成功的情况下才将当前线程加入等待队列。如下图,等待队列中节点线程按照顺序一个接一个尝试去获取共享资源的使用权。而某一时刻头结点线程准备尝试获取的同时另外一条线程闯入,新线程并非直接加入等待队列的尾部,而是先跟头结点线程竞争获取资源。闯入线程如果成功获取共享资源则直接执行,头结点线程则继续等待下一次尝试。如此一来闯入线程成功插队,后来的线程比早到的线程先执行,说明AQS锁获取算法是不严格公平的

3、ReentrantLock

4、CountDownLatch

底层还是对AQS的共享变量state做操作,每个线程执行完操作后,执行countDown操作扣减state,当state扣减为0时,唤醒调用await方法的线程,向下执行。

image.png

初始化state = 4,开启四个线程,每个线程执行完成后扣减state,扣减到0后,调用await方法的主线程继续向下执行。

image.png

5、CyclicBarrier

初始化屏障总数,每当一个线程调用一个await方法,则计数-1,减到0后,唤醒所有线程,继续向下执行。 原理是调用了reentrantLock的signalAll方法。

image.png

第一组线程全部到达屏障点后,屏障值恢复,下一组线程可继续复用。

6、Semaphore

Semaphore是信号量,不属于锁,多线程尝试获取信号量,获取成功后,向下执行,因为允许Semaphore持有多个信号量,允许多个线程获取,所以底层调用的是AQS的tryAcquireShared方法。本质上还是通过CAS和队列的方式实现。

image.png

只有一个信号量,多个线程轮流获取释放,依次顺序执行。 image.png

7、ConcurrentHashMap