前置知识:
可重入锁又叫递归锁,同一个线程在外层方法获取锁的时候,在进入该线程内层方法会自动获取锁,不会因为之前已经获取过还没释放就阻塞 同一个线程可以多次获得同一把锁
每个锁对象都有一个锁计数器和一个指向持有该锁的线程的指针
隐式锁:synchronized
显式锁:ReentrantLock 显式锁指的是需要lock与unlock 加几次就必须解锁几次 不然其他的线程没办法获取锁
LockSupport:线程等待唤醒机制(wait/notify)改良加强版
传统:就是before lockstupport出现之前的用法
synchronized与wait notify
1、wait和notify必须要跟synchronized一起使用 否则不合法
2、一定是先阻塞再有人唤醒
condition与await与signal
他们都有两个约束1、线程都要先持有锁,2必须先等待后唤醒,线程才能被唤醒
after:
LockSupport:通过park unpark 来阻塞和唤醒线程 不需要显式的加锁解锁了
通过许可证来控制 而且可以先通知再阻塞
为什么可以先唤醒线程后阻塞线程?
因为Unpark获得了一个凭证,之后再调用park方法 ,就可以名正言顺的凭证消费,故不会阻塞
为什么唤醒两次后阻塞两次,但是最终结果还会阻塞线程?
因为凭证的数量最多为1,连续调用两次unpark和调用一次unpark效果一样,只会增加一个凭证,而调用两次park需要消费两个凭证,证不够,不能放行
两个车 只要一个通行证,证不够,所以不能通行。
正题:AQS抽象同步队列
AbstractQueuedSynchronizer:抽象队列同步器
AQS是什么? ----->变量+队列 是ReentrantLock 、 CountDwnLatch ReentrantReadWriteLock的、Semaphore的带头大哥 是一种统一的规范
为什么有AQS->有阻塞就需要排队,实现排队就必然需要队列
AQS是个队列控制的同步器 队列里面呢 装的是线程节点
AQS=state+CLH队列
Node=waitStatus+前后指针
从ReentrantLock入手来分析AQS
ReentrantLock加锁过程:1、尝试加锁 2.加锁失败,线程入队列 3、线程入队列后,进入阻塞状态
lock() 第一个线程占用
tryAcquire(arg)
addWaiter(Node.EXCLUSIVE)
acquireQueued(addWaiter(Node.EXCLUSIVE),arg))
在锁里面有一个state字段跟一个当前线程的字段用来做标志
队列中 有一个头结点一个尾结点 第一个节点是一个没有数据的节点 让他作为头结点 每一次进来一个线程就让第一个节点指向他 然后他成为尾结点
比方线程A此时占用了资源 线程B(此时在同步队列中)多次请求无果后 会被locksport给park(挂起)住 此时才算是真正的在“排队”坐稳了位置了
A办完业务了 此时要unlock 会将 “当前占用线程”改为null 并且设置state为0
此时会唤醒h 也就是抽象同步队列的头结点 头结点去唤醒下一个节点 也就是此时的B B此时会被唤醒 被唤醒之后B会继续尝试去抢占资源 此时A已经离开 并且State为0 B成功抢占到资源
C继续B的流程
然后会让B节点变成新的哨兵节点占位