Java并发源码分析之AQS及ReentrantLock

关联文章:
Java并发源码分析之Semaphore
Java并发源码分析之ReentrantReadWriteLock
Java并发源码分析之Condition
Java并发源码分析之CyclicBarrier
Java并发源码分析之CountDownLatch

工作原理概要

AQS全称是AbstractQueuedSynchronizer,抽象同步队列,是用来构建锁或其他组件的基础框架,相关类图如下:
AQS及ReentrantLock类图
AbstractOwnableSynchronizer:抽象类,定义了独占锁线程,exclusiveOwnerThread
AbstractQueuedSynchronizer:抽象类,继承了AbstractOwnableSynchronizer,里面包含内部类Node
Lock:接口,定义了获取锁和释放锁的方法
ReentrantLock:类,实现了Lock,里面包含三个内部类Sync、NonfairSync、FairSync
Sync:类,继承了AbstractQueuedSynchronizer,重写了tryRelease方法
NonfairSync:类,继承了Sync,重写了tryAcquire
FairSync:类,继承了Sync,重写了tryAcquire

原理概述

在AbstractOwnableSynchronizer中定义了一个exclusiveOwnerThread变量,用来表示获取到排它锁的线程。
AQS中定义state,标识同步状态,0代表锁未被占用,1代表锁已被占用;在排它锁中表示可重入次数,在共享锁中表示剩余共享锁的个数。
AQS里面有两种队列,同步队列等待队列,通过内部类Node实现的,等待队列是用来实现类似于Object的wait和notify的功能。同步队列是一个FIFO的双向链表,便于对里面的节点进行增删操作。head指向同步队列头部,是一个空节点,不存储信息,tail指向同步队列的队尾。

AQS中关键变量

// 同步队列的头节点(不包含线程信息)
private transient volatile Node head;
// 同步队列的尾节点
private transient volatile Node tail;
// 同步状态,0代表锁未被占用,1代表锁已被占用;在排它锁中表示可重入次数,在共享锁中表示剩余共享锁的个数
private volatile int state;
// 内部类,队列节点
static final class Node {
        // 共享锁节点
        static final Node SHARED = new Node();
        // 排它锁节点
        static final Node EXCLUSIVE = null;
  
 		/** 等待状态常量 */
        // waitStatus的状态, 值小于0都可以认为是等待状态, 大于0则是无效状态, 需要将其从同步队列中处理掉
        // 取消状态, 不想申请或释放锁了
        static final int CANCELLED =  1;
        // 等待通知状态, 当节点变为这个状态, 说明后继节点等待被唤醒
        static final int SIGNAL    = -1;
        // 条件状态, await/signal场景下使用
        static final int CONDITION = -2;
        // 常用作共享锁模式, 表示当前节点SHARED后继节点能够得以执行
        static final int PROPAGATE = -3;

        // 等待状态
        volatile int waitStatus;
 		// 前驱节点指针
        volatile Node prev;
  		// 后继节点指针
        volatile Node next;
  		// 当前线程
        volatile Thread thread;
  		// 同步队列中用来标记节点类型,等待队列中用来指向后继节点
        Node nextWaiter;
}

AQS中方法

抽象方法

AQS采用的是模板模式构建的,其内部除了提供并发操作核心方法以及同步队列操作外,还提供了一些模板方法让子类实现。

// 尝试获取排它锁 
protected boolean tryAcquire(int arg) {
  	throw new UnsupportedOperationException();
}
// 尝试释放排它锁
protected boolean tryRelease(int arg) {
 	 throw new UnsupportedOperationException();
}
// 尝试获取共享锁
protected int tryAcquireShared(int arg) {
  	throw new UnsupportedOperationException();
}
// 尝试释放共享锁
protected boolean tryReleaseShared(int arg) {
		throw new UnsupportedOperationException();
}

主要入口方法

 // 获取排它锁,不响应中断
acquire(int arg)
 // 获取排它锁,响应中断
 acquireInterruptibly(int arg)
 // 带有超时时间获取排它锁,响应中断
 tryAcquireNanos(int arg, long nanosTimeout)
// 释放排它锁
 release(int arg)
// 获取共享锁,不响应中断
 acquireShared(int arg)
// 获取共享锁,响应中断
acquireSharedInterruptibly(int arg)
// 带有超时时间获取共享锁,响应中断
tryAcquireSharedNanos(int arg, long nanosTimeout)
// 释放共享锁
releaseShared(int arg)

线程中断,当线程执行Thread.interrupt()方法时,会中断线程,这里的中断并不是线程停止执行,只是设置一个线程中断标识。响应中断,即获取到这个中断标识,然后进行处理,AQS是抛出InterruptedException异常。
由于代码都大致相同,这里只解析常用的lock和unlock

ReentrantLock获取锁流程

构造函数

// 默认为非公平锁
public ReentrantLock() {
	sync = new NonfairSync();
}

// 传参判定使用非公平锁或公平锁
public ReentrantLock(boolean fair) {
	sync = fair ? new FairSync() : new NonfairSync();
}

公平锁,当线程请求获取锁时,如果同步队列中存在等待的线程,则放入队列尾部进行排队等待。
非公平锁,直接请求获取锁。获取未成功,再将线程放入队列尾部进行排队等待。

1、ReentrantLock–获取锁入口

public void lock() {
	sync.lock();
}

// 非公平锁实现
final void lock() {
	if (compareAndSetState(0, 1))
 		// 获取成功,则设置独占锁线程变量为当前线程
 		setExclusiveOwnerThread(Thread.currentThread());
	else
 		// AQS中的方法
 		acquire(1);
}

// 公平锁实现
final void lock() {
	// AQS中的方法
	acquire(1);
}

非公平锁的实现中,在调用AQS中的acquire方法之前,先尝试通过CAS将state的值从0更新成1

2、AQS–acquire获取锁

// 获取排它锁
public final void acquire(int arg) {
    // tryAcquire尝试获取锁,获取失败封装成独占节点加入同步队列;
    // 加入队列失败,将线程中断,不再获取锁
    if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

3、ReentrantLock–tryAcquire尝试获取锁

// 非公平锁实现
protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    // state=0,说明此时没有线程获取锁,再次尝试获取锁
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            // 获取成功,设置独占锁线程变量为当前线程,返回成功
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // state !=0,判断是否是当前线程再次获取锁,可重入
    else if (current == getExclusiveOwnerThread()) {
        // 将state+1
        int nextc = c + acquires;
        // state<0,肯定是出现异常了,正常情况下state>=0
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        // 设置state,并返回成功
        setState(nextc);
        return true;
    }
    return false;
}


// 公平锁实现
protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    // state=0,说明此时没有线程获取锁,再次尝试获取锁
    if (c == 0) {
        // 判断同步队列是否有节点,没有节点才能申请获取锁
        if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
            // 获取成功,设置独占锁线程变量
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 跟上面一致,判断可重入
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

尝试获取排它锁,非公平锁先判断同步状态是否为0,为0则通过CAS更新同步状态,更新成功即获取锁成功;如果不为0,则需要判断是否是同一线程再次获取锁,ReentrantLock是支持可重入的,就要用到了父类变量exclusiveOwnerThread,是同一线程则将state+1,未成功返回获取锁失败。
公平锁多了一步判断队列是否为空,其他的一致。

4、AQS–addWaiter将线程封装成独占节点并添加到队列尾部

// mode=Node.EXCLUSIVE,返回新生成的节点
private Node addWaiter(Node mode) {
    // 将当前线程封装成Node节点
    Node node = new Node(Thread.currentThread(), mode);
    // node应该放置在队列尾部,即尾节点的后继节点,先获取尾节点pred
    Node pred = tail;
    // pred不为空,先尝试通过CAS操作将node放置到队列尾部
    if (pred != null) {
        node.prev = pred;
        // compareAndSetTail中会将tail指针指向node
        if (compareAndSetTail(pred, node)) {
            // 设置成功,将尾节点的后继指针指向node
            pred.next = node;
            return node;
        }
    }
    // 队列为空或者CAS操作失败
    enq(node);
    return node;
}

private Node enq(final Node node) {
    // 自旋
    for (;;) {
        Node t = tail;
        // 尾节点为空,则同步队列为空,先构建同步队列
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            // 跟上面一致,CAS操作
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

enq方法多了一步初始化队列的操作,CAS操作保证每次只有一个节点可以添加到队列中,自旋保证所有节点都可以添加到队列中。

5、AQS–acquireQueued自旋等待,条件满足再次申请获取锁

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        // 中断标志
        boolean interrupted = false;
        // 自旋
        for (;;) {
            // 获取节点的前驱节点
            final Node p = node.predecessor();
            // 当前驱节点为头节点时,尝试获取锁
            if (p == head && tryAcquire(arg)) {
                // 获取成功,将当前节点设置为新的头节点
                setHead(node);
                // 原来的头节点从链表中断开,便于垃圾回收
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            // 如果前驱节点不是头节点,获取尝试获取锁失败,判断是否需要挂起线程
            if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            // failed正常是false,不会走到这里。如果中间出现异常,说明当前线程已经无法正常申请锁了,则取消申请
            cancelAcquire(node);
    }
}

// 设置头节点,会将节点内容清空
private void setHead(Node node) {
    head = node;
    node.thread = null;
    node.prev = null;
}

// pred:前驱节点,node:当前节点
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    // 获取前驱节点的等待状态
    int ws = pred.waitStatus;
    // 等待状态为等待唤醒
    if (ws == Node.SIGNAL)
        return true;
    // 无效状态,将node向前寻找,寻找到有效的节点并将node放置在其后
    if (ws > 0) {
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        // 将等待状态设置为等待唤醒
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

// 挂起当前线程并且判断是否已中断
private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

队列中的节点除了头节点的后继节点可以尝试获取锁之外,其他节点都会进行阻塞,同时其前驱节点的等待状态为等待唤醒。当有其他线程释放锁之后,队列中的节点依次出队进行尝试获取锁。

6、AQS–cancelAcquire取消申请锁

private void cancelAcquire(Node node) {
    // 节点为空,直接返回
    if (node == null)
        return;
    // 将节点线程设置为null
    node.thread = null;
    
    // 获取前驱节点
    Node pred = node.prev;
    // 前驱节点ws>0,说明前驱节点为无效节点,向前查找到最近的有效节点
    while (pred.waitStatus > 0)
        node.prev = pred = pred.prev;

    // 获取前驱节点的后继节点
    Node predNext = pred.next;

    // 将当前节点的等待状态设置为取消状态
    node.waitStatus = Node.CANCELLED;

    // 如果当前节点是尾节点,将前驱节点设置从尾节点
    if (node == tail && compareAndSetTail(node, pred)) {
        // 将前驱节点的后继节点设置为null
        compareAndSetNext(pred, predNext, null);
    } else {
        // 如果node不是尾节点,说明node有后继节点,移除node节点需要保证node的后继节点不受影响,因此需要根据情况觉得是否唤醒node后继节点
        int ws;
        // 如果node的后继节点需要唤醒信号,并且pred可以剔红,则将pred.next = node.next
        if (pred != head &&
                ((ws = pred.waitStatus) == Node.SIGNAL ||
                        (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
                pred.thread != null) {
            // 通过CAS将node的后继节点设置为前驱节点的后继节点,即将node节点从链表里拿出来
            Node next = node.next;
            if (next != null && next.waitStatus <= 0)
                compareAndSetNext(pred, predNext, next);
        // pred节点无法正常提供给后继节点唤醒信号,则直接唤醒node的后继节点
        } else {
            // 唤醒node的后继节点
            unparkSuccessor(node);
        }
        // 将node节点的next指向自己,方便垃圾回收
        node.next = node; // help GC
    }
}

当node节点为同步队列中间节点时,需要判断pred节点是否可以提供node后继节点唤醒信号。
可以提供唤醒信号指的是,pred不为头节点 & (pred的等待状态为SIGNAL | pred的等待状态通过CAS成功修改为SIGNAL) & pred节点的线程不为null
可以提供唤醒信号,直接将node节点从链表中移除即可。无法提供唤醒信号,则需要直接唤醒node的后继节点,因为在其他线程释放锁时,无法再唤醒后继节点的线程了,只能在此时唤醒。

7、AQS–unparkSuccessor唤醒后继节点

// 唤醒node节点的后继节点
private void unparkSuccessor(Node node) {
    // 获取等待状态
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
    // 获取node节点的后继节点
    Node s = node.next;
    // 如果s为null, 或者waitStatus大于0(为CANCELLED, 取消状态)
    if (s == null || s.waitStatus > 0) {
        s = null;
        // 从尾节点开始遍历, 拿到离头节点的最近的有效节点
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    // s不为null, 唤醒s节点的线程
    if (s != null)
        LockSupport.unpark(s.thread);
}

ReentrantLock释放锁流程

1、ReentrantLock–unlock释放锁入口

public void unlock() {
	sync.release(1);
}

2、AQS–release释放锁

public final boolean release(int arg) {
    // 尝试释放锁
    if (tryRelease(arg)) {
        // 释放成功
        Node h = head;
        // 头节点不为空,并且头节点等待状态不为0
        if (h != null && h.waitStatus != 0)
            // 唤醒头节点的后继节点
            unparkSuccessor(h);
        return true;
    }
    return false;
}

3、ReentrantLock–tryRelease尝试释放锁

protected final boolean tryRelease(int releases) {
    // 计算释放后的state的值
    int c = getState() - releases;
    // 独占锁线程变量不是当前线程,抛出异常
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    // 如果c-0,表示锁已经完全释放了
    if (c == 0) {
        // 释放成功
        free = true;
        // 独占锁线程变量设置为null
        setExclusiveOwnerThread(null);
    }
    // 设置新的state,未完全释放即将state-1
    setState(c);
    return free;
}

当同步状态state>0时,说明未完全释放锁,该方法返回false,不唤醒队列中的节点

ReentrantLock–tryLock获取锁流程

public boolean tryLock() {
	// 调用非公平锁的nonfairTryAcquire,与上述一致
	return sync.nonfairTryAcquire(1);
}

tryLock只尝试一次获取锁,获取失败就返回失败,不会在同步队列中等待

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值