AQS相关笔记

电脑修了快20天了,还没修好,我服了。。。

也没有好记笔记和学习的地方,所以干脆在这里记笔记好了。

AQS

AQS具备特性:

1. 阻塞等待队列

2. 共享/独占

3. 公平/非公平

4. 可重入

5. 允许中断

ReetrantLock

阻塞:

LockSupport.park();

唤醒:

LockSupport.unpark(Thread t);

Lock锁伪代码:

 

 Lock

三大核心原理:

自旋、LockSupport、CAS、队列;

怎么获取锁?获取锁的是哪个线程?依赖的什么东西构建的队列?

公平

exclusiveOwnerThread 当前获取锁的线程是谁?

state  状态器 - 用于记录当前锁的状态 int类型,默认值为0

基于Node内部类构建队列,里面主要有 head、tail、thread属性。它是一个双向链表。

ReentrantLock

lock方法内部是调用的aquire(1):

aquire方法在父类:AbstractQueuedSynchronizer内部,aquire方法源码如下:

public final void acquire(int arg) {
    if(!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXECUTIVE), arg)){
        selfInterrupt();  // 外围程序员自己定义的代码也要能够识别到中断信号,把中断信号往外传
    }  
}

这里有一个tryAcquire(arg)的方法,在其父类:AbstractQueuedSynchronizer 是一个空的实现,需要子类去实现它。

其中ReentrantLock源码该方法如下:

 

 1. 如果当前state为0,并且当前队列里面没有人在等,并且cas更改数据成功,那么就拿到锁了,并且更改 exclusiveOwnerThread 为当前获取锁的当前线程。

 2. 如果当前state不为0,但是如果当前持有锁的线程是当前线程自己,那么state + 1;如果持有锁的当前线程不是自己,那么就return false,即拿不到锁。

图解结构表示为:

 

 如果像t1,t2,t3这些线程没有加锁成功,就会进入  acquireQueued(addWaiter(Node.EXECUTIVE), arg) 方法的逻辑:

addWaiter方法源码如下:

addWaiter(Node.EXECUTIVE):线程入队,Node节点,Node对Thread引用

   Node:共享属性,独占属性

  创建节点Node = pre,next,waitstate,thread 重要属性

  waitState 节点的生命状态:信号量

      • SINGAL = -1  // 可被唤醒
      • CANCELLED = 1  // 代表出现异常,中断引起的,需要废弃结束
      • CONDITION = -2  // 条件等待
      • PROGATATE -3  // 传播
      • 0 - 初始状态Init状态

为了保证所有的阻塞对象能被唤醒,入队时也需要保证安全

compareAndSetTail(t, node) 入队也存在竞争

// 当前节点,线程要开始阻塞

acquireQueued(Node(currentThread), arg)

   节点在阻塞之前还得再尝试一次获取锁

    1. 能够获取到,节点出队,并且把head往后面挪一个节点,新的头结点就是当前节点。

    2. 不能获取到,阻塞等待被唤醒。

      1. 首先第一轮循环修改head的状态,修改成SINGAL 标记出可以被唤醒。

      2. 第二轮循环循环,阻塞线程。parkAndCheckInterrupt(),并且判断是否是有中断信号唤醒的!

acquireQueued(Node(currentThread), arg) 方法内部会有一个自旋的过程(for(;;)),在两次尝试都获取不到锁之后,会进入下面的方法判断,是否可以进入可被唤醒状态。

boolean shouldParkAfterFailedAcquire(Node pred, Node node) 分别代表前驱结点和当前节点

如果前驱节点对应状态为SINGAL (可被唤醒),则返回true. -- 之后就会进入阻塞状态。

如果前驱节点对应状态值 > 0 ,则代表出现了异常,

如果前驱节点对应状态值 < 0,则把前驱节点状态 改为 SINGAL(通过cas操作改变)

 

waitState = 0 -> -1 head节点为什么改到 -1,因为持有锁的线程 T0 在释放锁的时候,得判断head节点的waitstate是否 != 0,如果!=0成立,会再把waitState = -1 -> 0,要想唤醒排队的第一个线程T1,T1唤醒再接着走循环,去抢锁,可能会再失败(在非公平锁场景下),此时可能有线程T3持有了锁!T1可能再次被阻塞,head节点状态需要再一次经历两轮循环:waitstate =0 -> -1

 

Park阻塞线程唤醒有两种方式:

1. 中断

2. relase

除了lock方法以外,还有一个lockInterruptibly()的方法,在其内部,可以发现,如果发现中断,是直接抛出中断异常的,最终调用finally代码块中的cancelAcquire(node)方法。

线程中断被唤醒,抛异常。

  cancelAcquire(node): // 标注当前节点的生命状态为cancelled.

 

 

 参考:https://blog.csdn.net/qq_40239169/article/details/125534173

 

end!

posted @ 2022-10-28 08:49  君莫笑我十年游  阅读(54)  评论(0编辑  收藏  举报