门户网站建设重要性,怎么修改网站模版,会员制营销方案案例,网络营销策略1.前言
AQS是AbstractQueuedSynchronizer#xff08;抽象同步队列#xff09;的简写#xff0c;它是实现同步器的基础组件#xff0c;并发包下的锁就是通过AQS实现的。作为开发者可能并不会直接用到AQS#xff0c;但是知道其原理对于架构设计还是很有帮助的。
那为什么说…1.前言
AQS是AbstractQueuedSynchronizer抽象同步队列的简写它是实现同步器的基础组件并发包下的锁就是通过AQS实现的。作为开发者可能并不会直接用到AQS但是知道其原理对于架构设计还是很有帮助的。
那为什么说浅谈呢因为我也仅仅是根据书加上自己的想法来看AQS。
2.LockSupport工具类
在正式讲解AQS之前我们需要先了解一下LockSupport类他的主要作用就是用来阻塞和唤醒线程。
LockSupport类与每个使用它的线程都会关联一个许可证在默认情况下调用LockSupport类的方法的线程是不持有许可证的。LockSupport是通过Unsafe类实现的。接下来我们介绍一下LockSupport类的主要方法。
park()
前面我们已经说过LockSupport类与每个使用它的线程都会关联一个许可证如果调用park方法的线程持有许可证那么就会立马返回否则就会被阻塞挂起。
System.out.println(begin park!);
LockSupport.park();
System.out.println(end park!);上述代码会在输出begin park之后进入阻塞状态。因为默认情况下是不具有许可证的。
只有在其他线程调用unpark(Thread t)方法并且将该线程作为参数该线程才能返回。如果其他线程调用了该线程的interrupt()方法设置了中断标志或者线程被虚假唤醒parkNanos方法可以指定阻塞一段时间后自己唤醒所以可能会出现虚假唤醒那么该线程也会返回所以调用park()方法的时候最好使用循环条件判断方式。
不过使用park阻塞的线程被其他线程中断返回时并不会抛出InterruptedException。
park(Object blocker)
public static void park(Object blocker) {// 获取调用线程Thread t Thread.currentThread();// Thread类中有一个变量parkBlocker用来存放park方法传递的blocker对象// 设置该线程的blocker变量setBlocker(t, blocker);// 挂起线程UNSAFE.park(false, 0L);// 线程被激活以后清楚blocker变量setBlocker(t, null);
}当线程在没有持有许可证调用该方法时会被阻塞挂起同时blocker对象会被记录到线程内部。
我们可以使用LockSupport.park(this)这样当程序运行以后我们使用jstack pid打印线程堆栈可以查看到具体是哪个类被阻塞了。
unpark(Thread thread)
当一个线程调用unpark的时候如果作为参数的thread没有持有许可证则会让线程持有。
如果thread之前因为调用park阻塞挂起则调用unpark后会被唤醒。
如果thread之前还没有调用park则在调用unpark以后如果调用park则会立即返回。
3.AQS
初识AQS 由类图我们可以知道AQS是一个FIFO双向队列通过节点head和tail记录队首队尾元素。
Node
Node节点内部的SHARED用来标记该线程是在获取共享资源时被阻塞挂起放入AQS队列的EXCLUSIVE用来标识该线程是获取独占资源时被阻塞挂起放入AQS队列的。
在Node节点内部有一个成员变量waitStatus记录当前线程等待状态可以为CANCELLED线程被取消了、SIGNAL线程需要唤醒、CONDITION线程在条件队列里等待、PROPAGATE释放资源时需要通知其他节点。
ConditionObject
ConditionObject和Node一样是AQS的内部类。它用来结合锁实现线程同步其可以访问AQS的内部变量state和AQS阻塞队列。
ConditionObject是条件变量每个条件变量对应一个条件队列我们可以看到ConditionObject中有两个指针分别指向条件队列的队尾和队头。条件队列用来存放调用条件变量的await方法后被阻塞的线程。
state
在AQS中维护了一个单一变量state对于不同的实现其有不同的意义
在ReentrantLock中state表示重入式锁的可重入次数在ReentrantReadWriteLock中state的高16位用于表示读锁的可获取次数低16位用于表示写锁的可重入次数。state的类型是int占用4个字节
Shared与Interruptibly
方法名中带有Shared的方法表示获取或释放共享资源如acquireShared(int arg)
方法名中带有Interruptibly表示对中断做出响应当线程调用了带Interruptibly的方法如果这时被其他线程中断那么就会抛出InterruptedException返回。
AQS工作流程
独占方式下
获取资源
首先使用tryAcquire尝试获取资源获取成功直接返回失败则将当前线程封装为EXCLUSIVE的节点插入到AQS阻塞队列尾部并使用LockSupport.park(this)挂起自己。
public final void acquire(int arg) {// tryAcquire的具体实现要由子类来完成AQS中并不提供实现if (!tryAcquire(arg) // addWaiter的作用是将当前线程封装为独占类型的节点插入AQS阻塞队列并且将该节点返回// acquireQueuedacquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();
}释放资源
当一个线程调用 release(int arg) 方法时会尝试使用 tryRelease 操作释放资源这里是设置状态变量 state 的值然后调用LockSupport.unpark(thread) 方法激活 AQS 队列里面被阻塞的一个线程 (thread)。被激活的线程则使用tryAcquire尝试如果条件满足则激活继续向下运行否则被放入AQS继续阻塞。
public final boolean release(int arg) {if (tryRelease(arg)) {// 释放资源成功则尝试激活线程Node h head;if (h ! null h.waitStatus ! 0)unparkSuccessor(h);return true;}return false;
}共享方式下
获取资源
线程调用acquireShared(int arg)获取共享资源时会首先使用tryAcquireShared尝试获取资源修改state的值成功直接返回失败则将当前线程封装为Node.SHARED的Node节点插入到AQS阻塞队列的尾部。
public final void acquireShared(int arg) {if (tryAcquireShared(arg) 0)doAcquireShared(arg);
}释放资源
当一个线程调用 releaseShared(int arg) 时会尝试使用 tryReleaseShared 操作释放资 源这里是设置状态变量 state 的值然后使用 LockSupport.unpark(thread)激活 AQS 队 列里面被阻塞的一个线程 (thread)。跟独占模式下的流程差不多。
public final boolean releaseShared(int arg) {if (tryReleaseShared(arg)) {doReleaseShared();return true;}return false;
}注意
AQS类并没有提供tryAcquire、tryRelease、tryAcquireShare、tryReleaseShare方法这些方法都需要子类实现。
同时如果你想使用AQS实现一个自己的锁那你还需要根据情景重写isHeldExclusively方法用来判断锁是被当前线程共享还是独占。
条件变量
条件变量拥有一个条件队列条件队列要跟AQS阻塞队列区别开。
当多个线程同时调用lock.lock()方法获取锁时只有一个线程获取到了锁其 他线程会被转换为 Node 节点插入到 lock 锁对应的 AQS 阻塞队列里面并做自旋 CAS 尝试获取锁。 如果获取到锁的线程又调用了对应的条件变量的 await() 方法则该线程会释放获取 到的锁并被转换为 Node 节点插入到条件变量对应的条件队列里面。