全称是 AbstractQueuedSynchronizer,是阻塞式锁和相关的同步器工具的框架 特点 :
子类主要实现这样一些方法 (默认抛出UnsupportedOperationException)
释放锁的姿势
加锁解锁流程 先从构造器开始看,默认为非公平锁实现
NonfairSync 继承自 AQS 没有竞争时
第一个竞争出现时 :
Thread-1 执行了
当前线程进入 acquireQueued 逻辑
Thread-0 释放锁,进入tryRelease 流程,如果成功
当队列不为null,并且head的waitStatus = -1,进入unparkSuccessor 流程找到队列中离head最近的一个Node(没取消的),unpark恢复其运行,本例中即为Thread-1 回到 Thread-1 的acquireQueued 流程
回到 Thread - 1的 acquireQueued 流程
如果加锁成功(没有竞争),会设置
如果不巧又被 Thread - 4 占了先
不可打断模式 在此模式下,即使它被打断,仍会驻留在AQS队列中,等获得锁后方能继续运行(是继续运行!只是打断标记被设置为true)
可打断模式
每个条件变量其实就对应着一个等待队列,其实现类是 ConditionObject await 流程 开始 Thread - 0 持有锁,调用await,进入ConditionObject 的addConditionWaiter流程创建新的Node状态为 -2 (Node.CONDITION),关联Thread - 0,加入等待队列尾部
接下来进入AQS的fullyRelease流程,释放同步器上的锁
unpark AQS 队列中的下一个节点,竞争锁,假设没有其他竞争线程,那么Thread - 1竞争成功
park 阻塞 Thread - 0
signal 假设Thread - 1 要来唤醒 Thread - 0
进入 ConditionObject 的doSignal流程,取得等待队列中第一个 Node,即Thread - 0所在Node
执行transferForSignal 流程,将该Node 加入AQS队列尾部,将Thread - 0 的waitStatus改为0,Thread - 3的waitStatus改为 - 1
Thread - 1 释放锁,进入unlock流程。
当读操作远远高于写操作时,这时候使用读写锁让 读-读可以并发,提高性能。 类似于数据库中的select 。。。from 。。。lock in share mode 提供一个数据容器类内部分别使用读锁保护数据的read()方法,写锁保护数据的write()方法 注意事项
更新时,是先清缓存还是先更新数据库 先清缓存
先更新数据库
2)t2 执行 t.lock,这时进入读锁的 sync.acquireShared(1)流程,首先会进入tryAcquireShard流程。如果有写锁占据,那么tryAcquireShared返回-1 表示失败 tryAcquireShared 返回值表示
3)这时会进入 sync.doAcquireShared(1)流程,首先也是调用 addWaiter添加节点,不同之处在于节点被设置为 Node.SHARED 模式而非 Node. EXCLUSIVE模式,注意此时t2仍处于活跃状态
4)t2会看看自己的节点是不是老二,如果是,还会再次调用tryAcquireShared(1)来尝试获取锁 5)如果没有成功,在doAcquireShared 内 for (;;)循环一次,把前驱节点的waitStatus改为 -1,再for(;;)循环一次尝试tryAcquireShared(1)如果还不成功,那么在parkAndCheckInterrupt()处park
t3 r.lock, t4 w.lock 这种状态下,假设又有t3 加读锁和t4加写锁,这期间t1任然持有锁,就编程了下面样子
t1 w.unlock 这时会走到写锁的 sync.release(1)流程,调用sync.tryRelease(1)成功,变成下面的样子
接下来执行唤醒流程 sync.unparkSuccessor,即让老二恢复运行,这时 t2 在doAcquireShared 内 parkAndCheckInterrupt()处恢复运行 这回再来一次 for(;;)执行 tryAcquireShared成功则让读锁计数加一
这时t2 已经恢复运行,接下来t2调用 setHeadAndPropagate(node, 1),它原本所在节点被置为头节点
事情还没完,在setHeadAndPropagate 方法内还会检查下一个节点是否是 shared,如果是则调用 doReleaseShared 将head的状态从 -1 改为 0并唤醒老二,这时t3在 doAcquireShared 内 parkAncCheckInterrupt() 会恢复运行
这回再来一次for(;;)执行tryAcquireShared 成功则让读锁计数加一
这时t3 已经恢复运行,接下来t3 调用 setHeadAndPropagate(node,1),它原本所在节点被置为头节点
下一个节点不是shared了,因此不会继续唤醒t4所在节点 t2 r.unlock, t3 r.unlock t2 进入 sync.releaseShared(1)中,调用 tryReleaseShared(1)让计数减一,但由于计数为零
t3 进入 sync.releaseShared(1)中,调用tryReleaseShared(1)让计数减一,这回计数为零了,进入doReleaseShared()将头节点从-1改为0并唤醒老二,即
之后 t4 在acquireQueued 中 parkAndCheckInterrupt 处恢复运行,再次for(;;)这次自己是老二,并且没有其他竞争,tryAcquire(1)成功,修改头结点,流程结束