面试: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,加入。