JUC

一、Lock基本使用

ReentrantLock(重入锁) ReentrantReadWriteLock(读写锁)

二、ReentrantLock原理

基本概念

  1. 可重入,即同一个线程可以多次获取同一把锁而不导致死锁
  2. 公平锁和非公平锁,默认是非公平锁
  3. 非公平锁代码体现,一开始会尝试去抢占锁,有compareAndSetState方法
  4. 公平锁代码体现,没有前面的抢占逻辑,没有compareAndSetState方法

设计原理

  1. 获得锁和唤醒过程
  2. 基于AQS(AbstractQueuedSynchronizer)实现
    1. 定义:AQS 使用一个 FIFO 队列来管理尝试获取锁但未能成功的线程,并使用了一个整型变量表示锁的状态
    2. AQS状态管理,AQS定义两个属性
      1. 有一个volatile修饰的整型变量state,state=0表示无锁,state可以重入获取
      2. execlusiveOwnerThread,存储当前获得锁线程
    3. 队列机制:
      1. 当一个线程尝试获取锁但失败时,它会被封装成一个Node节点加入到 AQS 维护的同步队列尾部。每个节点都有pre和next指针,以便于线程之间的协调。队列中的线程会按照 FIFO 的顺序尝试获取锁。
      2. 当持有同步状态的线程执行完任务后调用了相应的释放锁,AQS将从等待队列的头部开始,唤醒第一个节点对应的线程,并允许它再次尝试获取同步状态
      3. Node节点定义和属性
        1. waitStatus,节点状态,包括:SIGNAL、
        2. thread,当前线程
    4. 阻塞和唤醒原理:
      1. 通过lockSupport的park();和unPark(threadId);
    5. AQS为什么是安全的
      1. 原子变量操作
        AQS使用了一个单一的int类型的volatile变量state来表示同步状态。compareAndSetState(int expect, int update)方法进行访问和修改。其中,compareAndSetState方法利用了CAS(Compare And Swap)机制,这是一种底层硬件支持的原子操作,能够在不使用锁的情况下实现对共享变量的安全更新。这意味着即使多个线程同时尝试修改state,也能够确保任意时刻只有一个线程能成功修改其值,从而避免了竞态条件。
      2. 内存屏障
        为了防止编译器优化或CPU乱序执行导致的数据一致性问题,AQS在关键位置插入了内存屏障(Memory Barrier)。这些屏障确保了某些指令不会被重排序,并强制刷新缓存到主内存,使得所有线程都能看到最新的状态变更。这对于维护同步状态的一致性至关重要。
      3. FIFO等待队列
        当一个线程试图获取同步状态但失败时(例如,因为另一个线程已经持有锁),该线程将被封装成一个节点并加入到一个由AQS管理的FIFO双向链表中等待。这种队列结构确保了线程可以按照申请锁的顺序依次获得锁,避免了饥饿现象的发生。此外,AQS还通过自旋与阻塞相结合的方式提高了性能:如果预计很快就能获取到锁,线程可能会先进行短暂的自旋;否则,则会被挂起以节省资源。
      4. 线程的唤醒
        一旦持有锁的线程释放了同步状态,AQS会从等待队列头部移除第一个节点,并尝试将其对应的线程唤醒。这样做的好处是保证了公平性,即最先请求锁的线程将优先获得锁。同时,这也意味着在高并发环境下,虽然可能有多个线程竞争同一把锁,但由于存在这样一个有序的调度机制,依然可以保证系统的稳定性和高效性。
  3. 其他
    1. Sync:这是ReentrantLoc的核心组件,继承自 AQS。它定义了加锁和解锁的基本行为。
    2. NonfairSync 和 FairSync:这两个类分别实现了非公平锁和公平锁的具体逻辑。它们都是 Sync 的子类,提供了不同的策略来处理锁的竞争。

三、Condition条件控制

Java中的Condition接口是并发包中的一部分,它为线程间的协调提供了比Object.wait()和Object.notify()更强大的功能。

基本概念

  1. Condition接口通常和Lock一起搭配使用,用来实现等待/通知模式。

同步队列和等待队列

  1. 同步队列,这是由AbstractQueuedSynchronizer(AQS)管理的一个FIFO队列,用于排队那些尝试获取锁但暂时未能成功的线程。(是一个双向链表)
  2. 等待队列,这是由ConditionObject(实现了Condition接口的具体类)管理的另一个FIFO队列,用于排队那些调用了await()方法而进入等待状态的线程。(是一个单向链表)

执行细节

  1. 当一个已经持有锁的线程调用Condition对象的await()方法时

    ,会发生以下操作:

    1. 它会释放持有的锁
    2. 封装成一个Node.CONDITION节点类型,并加入到等待队列中(尾部),进入等待状态
    3. 直到其他线程调用同一个Condition的signal()或signalAll()方法
  2. 当某个线程调用Condition对象的signal()或signalAll()方法时

    ,会发生以下操作:

    1. 等待队列头部的一个线程会被选中,并从等待队列移除
    2. 重新插入到同步队列(AQS)的尾部,准备再次尝试竞争锁
    3. 被唤醒的线程不会立即执行,需要与其他已经在同步队列(AQS)的现成竞争锁