1 AQS
AbstracQueuedSynchronizer简称AQS,很重要!有多重要呢?看看附图就知道了!
可以毫不夸张的说是AQS是java代码实现锁的基础,更是线程池的基础!
AQS的实现原理是一个先入先出的等待队列!
子类必须定义更改此状态的受保护方法,并定义该状态对于获取或释放此对象而言意味着什么。
2 AbstracQueuedSynchronizer 数据结构
由下示意图,我们简单分析以下内置Class Node的具体结构:
2.1 node状态
Node结点是对每一个等待获取资源的线程的封装
,其包含了需要同步的线程本身及其等待状态,如是否被阻塞、是否等待唤醒、是否已经被取消等。变量waitStatus则表示当前Node结点的等待状态,共有5种取值CANCELLED、SIGNAL、CONDITION、PROPAGATE、0。
2.2 数据结构
AbstractQueuedSynchronizer类底层的数据结构是使用双向链表,是队列的一种实现形式。其中Sync queue,即同步队列,是双向链表,包括head结点和tail结点,head结点主要用作后续的调度。而Condition queue不是必须的,其是一个单向链表,只有当使用Condition时,才会存在此单向链表。并且可能会有多个Condition queue。
3 AbstractQueuedSynchronizer
AbstractQueuedSynchronizer继承自AbstractOwnableSynchronizer抽象类,并且实现了Serializable接口,可以进行序列化。
其提供了两个方法设置独占模式线程已经获取独占模式线程。
4 如何上锁?lock
acquire说明—》
请求独占锁,忽略所有中断,至少执行一次tryAcquire,如果成功就返回,否则线程进入阻塞–唤醒两种状态切换中,直到tryAcquire成功。
以ReentrantLock为例,我们来简单了解下上锁的过程:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Sx3XyRqr-1571312124033)(assets/1569488822650.png)]
- tryAcquire通过cas操作来尝试占有锁,但是不见得能马上成功;
- acquire是真正的锁的过程,先继续tryAcquire,不成功则添加节点,加入队列,在循环中挂起,或者继续执行!
5 如何解锁? unlock?
unlock的过程则相对简单,主要实现就是将独占线程设为null,修改状态,最后就是释放锁!
- tryRelease为AQS子类实现,具体的状态释放;
- release则为AQS final实现,主要功能就是调用tryRelease,成功则取消挂起!
事实上关于普通的加锁和去锁,如上两节分析就足够了,在数据结构上也仅用到双向队列,并未涉及conditionQueue
通常情况下也是如此!那么ConditionQueue在什么情况被使用呢?且听下节分解!
6 ConditionObject简介
Condition是对象监视器的替代品,扩展了监视器的语义!
https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/Condition.html
https://coderanch.com/t/491354/java/await-wait
http://www.iocoder.cn/JUC/sike/Condition/
ConditionObject是AQS中的内部类,提供了条件锁的同步实现,实现了Condition接口,并且实现了其中的await(),signal(),signalALL()等方法。
ConditionObject主要是为并发编程中的同步提供了等待通知的实现方式,可以在不满足某个条件的时候挂起线程等待。直到满足某个条件的时候在唤醒线程。
可以说ConditionObject更加重要,使用条件锁的地方,都会或多或少与该类产生关联,简单看看调用就知道了!
await(), signal(),signalAll() 的功用和 wait(), notify(), notifyAll() 基本相同, 区别是, 基于 Condition 的 await(), signal(), signalAll() 使得我们可以在同一个锁的代码块内, 优雅地实现基于多个条件的线程间挂起与唤醒操作。
6.1 await源码分析
public final void await() throws InterruptedException {
if (Thread.interrupted()) {
// 抛出异常...
}
// 创建包含当前线程的节点并添加到[条件等待队列]
Node node = addConditionWaiter();
// 释放锁的所有重入计数,失败则中断线程,并将节点的状态置为 CANCELD
long savedState = fullyRelease(node);
int interruptMode = 0;
// 判断该节点是否在[同步等待队列]
while (!isOnSyncQueue(node)) {
// 不在的话,则线程进入阻塞状态
LockSupport.park(this);
// 表示线程被唤醒的操作:确定中断模式并退出循环
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) {
break;
}
}
// 成功获取独占锁后,并判断 interruptMode 的值
if (acquireQueued(node, savedState) && interruptMode != THROW_IE) {
interruptMode = REINTERRUPT;
}
if (node.nextWaiter != null) {
// 清除条件等待队列上节点状态不为 CONDITION 的节点
unlinkCancelledWaiters();
}
if (interruptMode != 0) {
// 根据 interruptMode 作出对应的动作
// 若为 THROW_IE 则抛出异常中断线程
// 若为 REINTERRUPT 则设置线程中断标记位
reportInterruptAfterWait(interruptMode);
}
}
6.2 signal源码分析
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
/*
* If cannot change waitStatus, the node has been cancelled.
*/
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
/*
* Splice onto queue and try to set waitStatus of predecessor to
* indicate that thread is (probably) waiting. If cancelled or
* attempt to set waitStatus fails, wake up to resync (in which
* case the waitStatus can be transiently and harmlessly wrong).
*/
Node p = enq(node);
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
7 为什么wait和notify notifyAll需要和Synchronized放在一起?
Marco Ehrentreich写道:
这意味着wait(),notify()与实例的一个唯一的内部锁直接相关,而Condition中的方法可以与多个锁结合使用。 这使您可以使用等待功能并通过多个等待集进行通知。
具有一个锁的多个条件var的情况更为常见(如果事实上,Lock / Condition类不允许将Condition类与多个Lock一起使用)。
典型的情况是在队列上加锁,有两个不同的条件变量:一个用于队列何时为空,等待数据,另一个用于队列何时为满,等待空间。
8 为什么wait和notify是Object方法
简单说法:因为synchronized可以是任意对象所以任意对象可以调用wait和notify
专业说法:方法在操作同步线程时,都必须要标识它们操作线程的锁,只有同一锁的被等待线程可以被同一个锁的notify唤醒,不可以对不同锁的线程进行唤醒,也就是等待和唤醒必须是同一锁,而锁可以是任意对象,所以可以被任意对象调用的方法是定义在object类中。
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
机房租用,北京机房租用,IDC机房托管, http://www.fwqtg.net