面试:AQS 锁

█ 1 可重入锁(递归锁)

同一个线程在外层方法获取锁的时候,在进入该线程的内层方法 会自动获取锁。

锁对象是同一个对象。

ReentrantLock(显式) synchronized(隐式)都是可重入锁

避免死锁

█ 2 LockSupport

他是线程等待唤醒机制wait notify的加强版

其中的 park() unpark()的作用是 阻塞线程和接触阻塞线程


○ 3种让线程等待和唤醒的方法:

图片

○ 2.1 object类中的wait notify

实现线程等待和唤醒

同一个代码块 必须用synchronized 否则 IllegalMonitorStateException

await notify 位置反转 无法唤醒


○ 2.2 Condition接口中的await signal

实现线程等待和唤醒

同一个锁块 否则 IllegalMonitorStateException

await signal 位置反转 无法唤醒

○ ○ 2.2.. 综上 传统的synchronized Lock

实现线程等待和唤醒 约束:

1)线程先要获得并持有锁,必须在锁块中

2)必须要先等待再唤醒

○ 2.3 LockSupport类中的park等待和unpark唤醒:

不需要上面的

图片

图片

○ 2.4 why可先unpark唤醒后阻塞线程?

因为unpark()获得了一个凭证,之后调用park方法,剋要消费掉凭证,所以不会阻塞。

代码

图片

○ 2.5 why唤醒unpark两次后阻塞两次,结过阻塞线程?

凭证的数量最多是1,连续调用两次unpark和调用一次 unpark效果一样,只会增加一个凭证;而调用两次park需要消费2个凭证,证不够,阻塞


两个unpark()一起调用的时候会阻塞,因为第一个凭证被消耗了,第二个没有拿到凭证

图片

█ 3.AQS 框架

他是用来构建锁 或者其他同步器组件 的 重量级 基础框架 及整个 JUC体系的基石,,,

AQS = state + CLH队列

图片

1 通过内置的FIFO队列 来完成资源获取线程的 排队工作,
2 并通过一个int类型变量 表示 持有锁的状态 。


加锁会导致阻塞,排队等候机制;

阻塞唤醒机制 主要用 CLH队列的变体实现,把暂时 获取不到锁的线程 加入到队列。封装成节点,通过CAS、自旋 和LockSupport.park()方式,维护state变量的状态,实现同步控制。

AbstractQueuedSynchronizer抽象的队列同步器

跟AQS有关的:ReentrantLock\CountDownLatch\ReentrantReadWriteLock\Semaphore 继承了AbstractQueuedSynchronizer


○ 3.1 JUC同步队列CLH

CLH队列,,AQS中的队列是CLH变体的 虚拟双向队列FIFO pre next Node(包了线程)

AQS的CLH队列有head和tail,另外有一个空的占位头节点(哨兵节点),最开始这个头节点的尾指针指向null

哨兵节点他的waitState=-1,当ThreadB被unpack出队之后,原来的哨兵早已被回收,现在的哨兵就是B在的位置,Node改成了null

○ 3.2 源码流程:

相似的:HashMap里面的KV键值对 是先被封装成了Node


流程:先看这个锁是否被占用,如果被占了就acquire(),如果当前CLH队列是空的,就添加哨兵节点和这个线程封装的Node;如果CLH不是空的就直接添加,一直比较状态码,看他的状态;
如果空了,CLH哨兵节点后面第一个安排出队 占用锁,当前的哨兵被GC,存放出队线程Node的位置变成哨兵节点。


函数有:

lock() 

aquire()   tryAquire(arg)   addWaiter(Node.EXCLUSIVE) acquireQueued(addWaiter(Node.EXCLUSIVE),arg)

unlock() sync.release(1) tryRelease() unpark() park被解除

AbstractQueuedSynchronizer 包括 Node head,tail ;volatile int state <0没人>;

Node里面有pre next Node ; volatile int waitStatus; Node有共享模式SHARED 和排他EXCLUSIVE

图片

图片

3.2.1 lock()

图片

3.2.2 aquire() tryAquire(arg) addWaiter(Node.EXCLUSIVE) acquireQueued(addWaiter(Node.EXCLUSIVE),arg)

图片

CLH队列有head和tail,另外有一个空的占位头节点,最开始这个头节点的尾指针指向null

哨兵节点他的waitState=-1,当ThreadB被unpack出队之后,原来的哨兵早已被回收,现在的哨兵就是B在的位置,Node改成了null

图片图片图片

3.2.3 unlock() sync.release(1) tryRelease() unpark() park被解除

非公平ReentrantLock,走lock(),false 就aquire尝试获取锁tryAquire ,如果获取不到,就排队aquireQueued,

对于排队addWaiter,要先队列初始化,enq哨兵节点

封装Node,加入。

posted @ 2021-03-31 08:32  千面鬼手大人  阅读(129)  评论(0编辑  收藏  举报
// 侧边栏目录 // https://blog-static.cnblogs.com/files/douzujun/marvin.nav.my1502.css