目录
简介
Java中的大部分同步类(Lock、Semaphore、ReentrantLock等)都是基于AbstractQueuedSynchronizer(简称为AQS)实现的。AQS是一种提供了原子式管理同步状态、阻塞和唤醒线程功能以及队列模型的简单框架。
在AQS中的锁类型有两种:分别是Exclusive(独占锁)和Share(共享锁)。
「独占锁」就是「每次都只有一个线程运行」,例如ReentrantLock
。
「共享锁」就是「同时可以多个线程运行」,如Semaphore、CountDownLatch、ReentrantReadWriteLock
。
原理
AQS核心思想是,如果被请求的共享资源空闲,那么就将当前请求资源的线程设置为有效的工作线程,将共享资源设置为锁定状态;如果共享资源被占用,则调用LockSupport().park()方法将Node中的线程状态改为WAITING,等待被唤醒或被中断
,就需要一定的阻塞等待唤醒机制来保证锁分配。这个机制主要用的是CLH队列的变体实现的,将暂时获取不到锁的线程加入到队列中。
CLH:Craig、Landin and Hagersten队列,是单向链表,AQS中的队列是CLH变体的虚拟双向队列(FIFO),AQS是通过将每条请求共享资源的线程封装成一个节点来实现锁的分配。
主要原理图如下:
AQS使用一个Volatile的int类型的成员变量来表示同步状态,通过内置的FIFO队列来完成资源获取的排队工作,通过CAS完成对State值的修改。
在FIFO队列中,头节点占有锁,也就是头节点才是锁的持有者,尾指针指向队列的最后一个等待线程节点,除了头节点和尾节点,节点之间都有前驱指针和后继指针。
在AQS中维护了一个共享变量state,标识当前的资源是否被线程持有,多线程竞争的时候,会去判断state是否为0,尝试的去把state修改为1。
1. AQS数据结构
AQS中最基本的数据结构——Node,Node即为上面CLH变体队列中的节点。
解释一下几个方法和属性值的含义:
方法和属性值 | 含义 |
---|---|
waitStatus | 当前节点在队列中的状态 |
thread | 表示处于该节点的线程 |
prev | 前驱指针 |
predecessor | 返回前驱节点,没有的话抛出npe |
nextWaiter | 指向下一个处于CONDITION状态的节点 |
next | 后继指针 |
线程两种锁的模式:
模式 | 含义 |
---|---|
SHARED(shared) | 表示线程以共享的模式等待锁 |
EXCLUSIVE(exclusive) | 表示线程正在以独占的方式等待锁 |
waitStatus有下面几个枚举值:
枚举 | 含义 |
---|---|
0 | 当一个Node被初始化的时候的默认值 |
CANCELLED(cancelled) | 为1,表示线程获取锁的请求已经取消了 |
CONDITION(condition) | 为-2,表示节点在等待队列中,节点线程等待唤醒 |
PROPAGATE(propagate) | 为-3,当前线程处在SHARED情况下,该字段才会使用 |
SIGNAL(signal) | 为-1,表示线程已经准备好了,就等资源释放了 |
2. 同步状态State
AQS中维护了一个名为state的字段,意为同步状态,是由Volatile修饰的,用于展示当前临界资源的获锁情况。
// java.util.concurrent.locks.AbstractQueuedSynchronizer
private volatile int state;
访问这个字段的方法:
方法名 | 描述 |
---|---|
protected final int getState() | 获取State的值 |
protected final void setState(int newState) | 设置State的值 |
protected final boolean compareAndSetState(int expect, int update) | 使用CAS方式更新State |
这几个方法都是Final修饰的,说明子类中无法重写它们。我们可以通过修改State字段表示的同步状态来实现多线程的独占模式和共享模式(加锁过程)。
【独占模式】
【共享模式 】