文章目录
一、AQS
AQS全称AbstractQueuedSynchronizer,意思是抽象队列同步器。ReentrantLock包含一个成员变量Snyc,ReentrantLock大部分功能依赖Snyc,它继承至AbstractQueuedSynchronizer。必须先弄清楚了AbstractQueuedSynchronizer才能理解ReentrantLock的原理。
1.1 AQS基本原理
AbstractQueuedSynchronizer是阻塞式锁和相关同步工具的框架,本篇主要讨论ReentrantLock,这里就简单介绍AQS基本原理。
AQS特点:
- 使用state属性来表示共享资源的状态(分为共享式和独占式),子类需要定义如何维护这个状态,控制如何释放锁和获取锁
- getState 获取state状态
- setState 设置state状态
- compareAndSetState 乐观锁机制设置state状态
- 独占模式只有一个线程能访问共享资源,共享模式可以多个线程访问共享资源
- 提供了基于FIFO的等待队列,类似于Monitor的EntryList
- 条件变量来实现等待唤醒机制,类似于Monitor的WaitSet
AQS子类实现方法:
- tryAcquire获取锁
//如果获取失败
if(!tryAcquire(arg)){
//进入队列等待 park
}
- tryRelease释放锁
//如果释放锁成功
if(tryRelease()){
//唤醒其他线程 unpark
}
- tryAcquireShare 共享方式获取锁
- tryReleaseShare 共享方式释放锁
- isHeldExclusively 是否持有独占锁
1.2 自定义锁
基于AQS自定义实现一个不可重入的独占锁
public class MyLock implements Lock {
public static void main(String[] args) {
MyLock lock = new MyLock();
new Thread(() -> {
lock.lock();
try {
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} finally {
lock.unlock();
}
},"t1").start();
new Thread(() -> {
lock.lock();
try {
//lock.lock();再次加锁,当前线程被阻塞
System.out.println(Thread.currentThread().getName());
} finally {
lock.unlock();
}
},"t2").start();
}
/**
* 独占锁,不可重入
*/
class Sync extends AbstractQueuedSynchronizer {
@Override
protected boolean tryAcquire(int arg) {
if (compareAndSetState(0, 1)) {
//上锁之后,设置锁的所有者为当前线程
setExclusiveOwnerThread(Thread.currentThread());
System.out.println("成功加锁");
return true;
}
return false;
}
@Override
protected boolean tryRelease(int arg) {
setExclusiveOwnerThread(null);
setState(0);
//假如先设置状态再设置锁的所有者为空,可能会导致,其他线程获取到锁的一瞬间,所有者还未改变,产生不可预知问题
//而state变量由volatile修饰,执行顺序不会改变,先设置所有者为空,就不会产生上述问题
//setExclusiveOwnerThread(null);
System.out.println("已解锁");
return true;
}
//锁是否被持有
@Override
protected boolean isHeldExclusively() {
return getState() == 1;
}
public Condition newCcondition() {
return new ConditionObject();
}
}
private Sync sync = new Sync();
/**
* 加锁,不成功的进入等待队列
*/
@Override
public void lock() {
sync.acquire(1);
}
/**
* 可打断加锁
*/
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
/**
* 尝试加锁一次
*/
@Override
public boolean tryLock() {
return sync.tryAcquire(1);
}
/**
* 尝试加锁,等待指定的时间
*/
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(time));
}
/**
* 解锁
*/
@Override
public void unlock() {
//解锁,唤醒正在阻塞的线程
sync.release(0);
}
/**
* 创建条件变量
*/
@Override
public Condition newCondition() {
return sync.newCcondition();
}
}
二、ReentrantLock
2.1 加锁流程
调用lock.lock()进入lock方法
进入同步器的lock方法
NonfairSync是非公平锁实现,可见,当前线程直接去修改state状态,即抢锁。如果此时有其他线程在队列(先进先出,双向链表实现)里面等待,对这些线程来说是不公平的,因此叫非公平锁。加锁成功就结束了,下面看下加锁失败的情况
2.1.1 加锁失败
加锁失败进入acquire(1),看下面代码的执行逻辑
-
再次尝试加锁成功,就不再往下执行
-
再次尝试加锁失败,先将线程加入队列尾部等待,再进入acquireQueued
-
-
加入队列失败,就将当前线程打断
2.2 释放锁流程
调用lock.unlock()
进入sync.release(1);
release在AbstractQueuedSynchronizer里面,说明公平与非公平的实现实际上是一样的。解锁流程
- tryRelease尝试释放锁,会判断当前线程是否是锁持有者,不是就报错
- 释放锁成功,唤醒头结点的下一个节点,头结点只记录了状态,线程为null,只负责找到下一未取消的节点唤醒
- 调用unpark恢复线程,从阻塞的 parkAndCheckInterrupt())方法里面继续执行
- 被唤醒的线程就再次进入循环尝试获取锁
- 成功获取锁之后,将被唤醒线程的节点指定为头节点,并将节点线程改为null
- 释放锁失败,返回false,之后由锁的实现类去处理,ReentrantLock没有对返回值进行处理。
2.2.1 可重入原理
从加锁看,再次重入,直接将状态+1
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//当前线程已经持有锁,代表是重入
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
//状态加1
setState(nextc);
return true;
}
return false;
}
从解锁看,再次重入,将状态减1,减到0说明解锁完成,再去唤醒队列中等待的线程
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
//解锁完成,并且都节点不为null,等待状态不为0,唤醒正在等待的线程
unparkSuccessor(h);
return true;
}
return false;
}
进入tryRelease
protected final boolean tryRelease(int releases) {
//状态减1
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
//减为0则返回true,代表解锁完成
free = true;
setExclusiveOwnerThread(null);
}
//状态不为0,将减小之后的值赋值回去,知道状态减为0
setState(c);
return free;
}
2.3 条件变量原理
线程在获取到锁之后,可以进入条件对象Condition等待,具体使用请看ReentrantLock使用,下面分析下源码
2.3.1 await
addConditionWaiter的作用是将当前线程放入等待队列,该等待队列被Condition对象持有。因此,每个条件对象都有自己的等待队列,每个线程可以进入不同的条件等待。
进入fullyRelease
在调用await之后会释放锁,这块释放锁release不是1,是因为可能发生了锁重入,要一次性将多层锁全部释放。如果此时线程被打断,释放锁失败就会抛出异常,节点状态修改成取消等待。返回之后通过 LockSupport.park(this);进入等待
2.3.2 signal
再来看看唤醒流程
等待队列的队头有节点,进行唤醒,进入doSignal
- (firstWaiter = first.nextWaiter) == null; firstWaiter指向了下一个节点,如果只有一个节点,则firstWaiter ==null为真
- firstWaiter ==null为真 会进入if块,将lastWaiter赋值为null
- 之后再执行transferForSignal(first),将线程唤醒,唤醒成功循环结束
- 如果线程已被打断导致唤醒失败,先将firstWaiter(它指向下一个节点)赋值给first,再判断firstWaiter 是否为空
- firstWaiter 为空说明没有下一个节点需要唤醒,结束循环
- firstWaiter 不为空,就继续循环,唤醒first,它已经指向上一次循环中first的下一节点
在transferForSignal方法里面也是调用LockSupport.unpark(node.thread);将线程唤醒