AQS(AbstractQueuedSynchronizer)源代码分析(一)——实现逻辑概览

      在学习java的锁机制时,一定会遇到AbstractQueuedSynchronizer类的实现。它几乎是java锁机制的核心,几乎所有的锁都是基于AbstractQueuedSynchronizer类实现的。所以很有必要看一下AbstractQueuedSynchronizer的源代码实现,但是直接看AQS源代码,很容易就绕晕了。主要是因为我们没能掌握AQS的运行机制,直接阅读细节实现,导致很难看得懂。所以本文重点说一下AQS的整体实现思路,方便后面进行源代码的分析。

       AbstractQueuedSynchronizer示例说明(高铁检票进站):

       AbstractQueuedSynchronizer的实现过程与高铁检票很类似,高铁检票有四个特征:

       1、检票口的数量是有限的(这里我们暂时只讨论一个检票口)

       2、等待检票的人数一定是大于检票口的数量的,所以会有排队(不排队的情况也有,只是比较少;不排队也是排队的一种特殊情况)

       3、如无意外,每次都是排在队首的人(即第一个人)能够检票进入;

       4、如果出现意外,比如:有老人或者其他原因,可能会有插队的情况(这里不考虑特殊通道)。

示例图如下:


检票场景描述:

      1、 当第一个人在检票的时候,后面所有的人都会等着(WAITING)。当第一个人检票完成后,我们会接收到一个信号“我可以检票了”(别人检票完成这个动作默认就给我们发出了一个信号)。然后我们就会去检票,如果检票成功,则进站;这个时候队列就少了一个人。

      2、如果等到我们检票的时候,突然有个人跑过来,说自己腿脚不方便或者其他XX(总之就是一顿说明,其实就是竞争检票口);很不幸,我们被他说服了,让他先检票;这个时候,我们又会处于等待状态(WAITING)。等到刚才那个人检票完成后,我们又接收到信号(他检票完了,我可以检票了)。此时,我们上前去进行检票。当然,这个时候还是有可能出现第二次竞争的,万一又跑来一个插队的,又是一顿说明。这次他没有说服你(我们竞争成功),这个时候想插队的那个人就会排到队队尾。我们检票进站。如下图:


上图演示了AQS的一个基本的处理过程,AQS就是基于这种方式来实现的。

       AbstractQueuedSynchronizer实现思路说明:

       AbstractQueuedSynchronizer维护了一个node队列和一个state状态,然后通过aquired(获取)和release(释放)两类方法操作这个队列和状态(留意一下,这里说的是“两类”方法,也就是说aquired和release方法有多个重载版本,后文再做讨论)。node队列表示每一个等待的线程,一个线程一个node节点(相当于等待检票的人),state表示当前锁的状态(相当于检票口的状态)。

       AbstractQueuedSynchronizer维护的node队列并不是我通常意义上说的queue队列,一种类似集合或者数组的东西。在AbstractQueuedSynchronizer中,node队列是通过一个一个对象组成的,而且这些队列之间相互的引用,也就形成了一种队列。看下图:


在每一个NODE中,都会持有两个引用(prev和next),用来分别指向自己的上一个节点和下一个节点,后文税说明具体的代码实现,这里只说实现思路。

       AQS操作的第一阶段:获取。如图:

       

上图简要的说明了AQS的获取过程,其实就是尝试修改sate状态的过程,如果修改成功,就会执行被锁定的内容,也就是我们需要同步的那部分代码。如果修改失败,则继续尝试修改(这种说法并不严谨,这里先这样理解,在第二阶段:释放,会具体说明这里的实现方式)。

      这里需要注意的是:

      1、每个进入的线程都会进行一次“尝试修改sate”的动作,相当于跟第一个检票人竞争检票口。如果竞争成功,则直接执行;如果失败,则排到node队列的末尾。

      2、上图的循环图标只是表示会循环尝试修改sate,但是并不会循环的去追加Node节点。每一个线程进行第一次尝试修改sate时,如果修改失败,AQS就会用当前线程创建一个node节点,追加在队列末尾。也就是说一个线程只会有一个节点。当进行第二次修改sate时,是不会再新增node了。

      AQS操作的第一阶段:释放。如图:


上图展示的是AQS的一个完整的获取和释放的过程。这里重点看release部分:

首先,在执行完被锁定的内容之后,就需要将sate还原为原始的状态,让其他线程可以执行。所以这里会尝试还原sate状态,如果还原成功,则唤醒WAITING中的线程。

其次,如果还原失败,这里还原失败并不会进行循环尝试。这是因为,在release时,还原失败并不是真正的失败了,而是因为sate被这个线程修改了多次,所以一次release并不能够将sate还原为“初始状态”,所以还需要等待下一次的release。

最后,还需要在说明一下“循环尝试修改sate”,当想成第一次尝试修改sate失败后,AQS就会把它挂起(相当于调用了Object.wait()方法),此时线程是处于WAITING睡眠状态的,需要其他线程或者操作将其唤醒。因此在其他线程执行完被锁定的内容后,会还原sate,同时也会唤醒处于WAITING中的线程。伪代码如下(AQS的代码并不是这样写的,这里只是方便理解):

//伪代码,获取
synchronized (user) {
	while(!tryAquired()){ //线程再次被唤醒时,会再次校验sate。这个校验的过程就是尝试修改sate的过程
		currentThread.wait();
	}
	doSomeThing();//执行被锁定的内容 
}

tryAquired(){
	if(sate == "sate的初始值"){
		//修改sate,可以是sate+1,也可以是sate-1,或者任何一种变化,这里是由AQS的子类决定的;
		sate++;
		return true;
	}else{
		return false
	}
}
//伪代码:释放
doSomeThing();//执行被锁定的内容完成之后
if(tryRelease()){//释放,还原sate,如果还原成功,则唤起线程
	Object.notify(); //唤起下一个线程,notify方法是随机唤起,这里只是说明唤起操作,AQS并不是使用notify方法唤醒的
};
      上面展示的获取和释放两个阶段是AQS核心的实现部分,主要在于可以理解AQS整个的运行机制时什么样子,以便于在下一节进行源代码分析时,不会被绕晕了。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值