AQS笔记&循环依赖
LockSupport是用来创建锁或其它同步类的基本线程阻塞原语
LockSupport类用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能,每个线程都有一个许可(permit)
permit只有两个值1和0,默认是0
可以把permit看成是一种(0,1)信号量,但与semaphore不同的是,许可的累加上线是1.
API方法:
park(阻塞)
unpark(唤醒)
使用方法:
LockSupport.park()
LockSupport.unpark(thread)
LockSupport的所有方法都是静态的,可以让线程在任意位置阻塞,阻塞之后也有对应的唤醒方法,归根结底,LockSupport调用的Unsafe的native代码。
permit默认是0,所以一开始调用park()方法,当前线程就会阻塞,直到别的线程将当前线程的permit设置为1,park方法会被唤醒。然后将park()方法再次设置为0并返回。
AQS
AbstractQueueSynchronizer
抽象队列同步器
是用来构建锁或其它同步组件的重量级基础框架,及整个JUC体系的基石,通过内置的FIFO队列来完成资源获取线程的排队工作,并通过一个int类型变量表示持有锁的状态
和AQS有关的:ReetrantLock、CountDownLatch、ReetrantReadWriteLock、Semphore、CyclicBarrier
ReetrantLock的Sync extends AbstractQueueSynchronizer
CountDownLatch的Sync extends AbstractQueueSynchronizer
ReetrantReadWriteLock的Sync extends AbstractQueueSynchronizer
Semphore的Sync extends AbstractQueueSynchronizer
锁与同步器的关系:
锁:面向锁的使用者,定义了程序员和锁交互的使用层API,隐藏了实现细节,你调用即可
同步器:面向锁的实现者,DougLee提出统一规范并简化了锁的实现,屏蔽了同步状态管理、阻塞线程排队和通知、唤醒机制。
为什么需要AQS?
加锁会导致阻塞。
抢占到资源的线程直接使用处理逻辑,抢不到资源的必然涉及一种排队等候机制。抢占资源失败的线程继续去等待(类似银行业务办理窗口都满了,暂时没有顾客只能去候客区排队等候),但等候线程仍然保留获取锁的可能且获取锁流程仍在继续(候客区的顾客也在等着叫号,轮到了再去受理窗口办理业务)
既然说到了排队等候机制,那么就一定会有某种队列形成,这样的队列是什么样的数据结构呢?
如果共享资源被占用,就需要一定的阻塞等待唤醒机制来保证锁分配。这个机制主要用的是CLH队列的变体实现的,将暂时获取不到锁的线程加入到队列中,这个队列就是AQS的抽象实现。它请求共享资源的线程封装成队列的节点(Node),通过CAS、自旋以及LockSupport.part()的方式,维护state变量的状态,使并发达到同步的控制效果。
AQS使用一个volatile的int类型的成员变量来表示同步状态,通过内置的FIFO队列来完成资源获取的排队工作,将每条要去抢占资源的线程封装成一个Node节点来实现锁的分配,通过CAS完成对State值的修改。
AQS Node<Thread>
hashmap Node<K,V>
AQS=State变量+CLH双端队列+Node<Thread>
Node 同尾部入队,头部出队,通过自旋等待。
Lock的lock和unlock都是聚合了一个AQS的子类实现的。
spring循环依赖
对构造注入不友好,对setter注入友好
spring对循环依赖的单例bean不报错,原型bean循环依赖会报错。
DefaultSingletonBeanRegistry
spring通过三级缓存hashmap来处理循环依赖
是否可以关闭循环依赖?
为什么使用三级缓存?使用二级缓存可以吗?使用一级缓存可以吗?
浙公网安备 33010602011771号