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