AQS是什么?就是这个玩意AbstractQueuedSynchronizer
那这个到底是什么?看到是个抽象类,看下子类都有什么
看到有ReentrantLock、Semaphore,这和可重入锁有什么关系,看下类的注释,看注释我只看第一句,嘿嘿嘿,大概意思是实现阻塞锁提供了队列的性质,比如semaphore。
那Semaphore是什么?看到java注释给了代码demo,稍微改造以下这段代码为切入点,简书帖不了代码格式的文本,搞半天不行,就截图吧。
这个伪代码的场景是实现数据库连接池最大连接数为10,相当于做了限制,超过10就要等待
运行结果
上面涉及到Semaphore的两个方法,就以这两个方法为切入点,不,先看下构造器干了什么事情。
有两个构造器
permits最终赋值给AQS(AbstractQueuedSynchronizer)的属性state,这个是AQS的核心属性,这个值我们设置的为10。
fair表示是否是公平锁,不传就是非公平锁。好接着再来看这两个方法
1)acquire()
这里写死了1传进去
tryAcquireShared(arg)有两个实现,一个是公平锁,一个是不公平锁。先看下非公平锁的整体实现
拿到state-1赋值给remaining,remaining<0直接return,如果remaining>=0,就通过cas把state修改为remaining。
看完可以知道tryAcquireShared(arg)非公平锁的实现就是拿到state-1后的值,如果值小于0直接返回,如果大于等于0就CAS,CAS如果没抢到就自旋直到remaining<0返回。嗯。。滴水不漏。。
如果小于0返回执行下一个方法,我们猜测下下面应该如何实现,如果容量(state)被用光了,当前线程要么进入等待,要么一直CAS(如果这样明显不合理),看下Doug Lea是怎么实现的。
看下addWaiter
用大腿拍下手就只知道这个enq方法要干啥,初始化(前面判断了tail不为空的情况,一开始肯定为空)和重试入队(没有入队成功就走下来了)。
那这里有个问题就是第一个节点进来的时候,队列长这个样子 ,head是个空节点,为什么不直接把当前节点作为头结点呢?先往后看
再来接着看doAcquireSharedInterruptibly方法,
这里可以看到当前节点被设为头结点后,又把头结点置空,这是为啥呢?难道设计成头结点为空有什么好处吗?
结论:整体看完非公平锁的acquire()可以知道,非公平锁也会入队,入队之后也是重试两次获取不到容量就进行等待,队列中也是头结点先进行获取容量(当然我是看过公平锁的实现了才用 “也”),只不过如果有新的线程进来时是直接获取容量的(不会先进行入队)。
上面那个问题尝试在release()中寻求答案,接着看下release()方法,一看这个方法没有分为公平锁和非公平锁,心里一凉。。
tryReleaseShared很简单就是把state+1,把之前acquire减掉值还原回去,看下doReleaseShared
看完之后还是不知道为啥要多出来一个空的头结点。。后续再研究下。。
至于公平锁,差异就是上图这个方法,主要含义就是多了个每次取获取容量的时候会判断是否头结点的后继是否为空,我们知道头结点是不参与竞争的,也就是头结点的后继是队列中的第一个。这个方法就是判断当前节点是不是第一个,第一个则可以获取容量,不是则不进行获取。