多线程:AQS的一些心得

版权声明:本博客为记录本人学习过程而开,内容大多从网上学习与整理所得,若侵权请告知! https://blog.csdn.net/Fly_as_tadpole/article/details/87916308

AQS作为JUC并发组件实现的核心。全称是AbstructQueuedSynchronizer,也就是同步队列器。

其内部实现主要的是一个state状态标识和基于FIFO一个同步队列

这个状态标识表明当前的资源是否是可以争取的,当state=0是,表示资源没有加锁;当state>0时,表示资源加锁。

关于这个队列,有几个属性:


1)队列是基于双向链表的,因此具有定制的节点结构信息。node

2)tail尾指针,指向队列的最后一个节点

3)头指针,指向队列的第一个节点(也即是   头节点)。头结点中包含有当前资源的同步状态信息。

4)waitStatus 节点等待标识。

=1时   SIGNAL   表明当前节点的后继节点所包含的线程需要运行。

=-1    CANCEL  表明当前节点线程取消

=-2    CONDITION 表明当前节点在等待condition  处于Condition等待队列

=-3    PROPAGATE  应用在共享锁中

 = 0   当前节点在同步队列中,等待获取锁


AQS是通过内部的同步队列来完成线程获取资源的排队工作

AQS可以实现独占锁和共享锁。

ReentrantLock实现的是独占锁;ReentrantReadWriteLock实现的是独占锁和共享锁;CountDownLatch实现的是共享锁。

因此,关于锁的获取和释放有以下几种方式:

1)独占式获取锁

首先,线程尝试获得锁tryAcquire,获取成功则函数返回,获得失败,addWaiter构造一个独占模式同步节点,如果前驱节点不为空则将该节点采用CAS方式放置到同步队列尾部,如果放置失败则说明当前节点位置存在并发竞争,进入enq方法进行添加,其实,enq方法也就是通过CAS自旋锁的方式不断地尝试来向队列中添加构造的节点;

如果节点最后成功地放置到队列里。执行acquireQueued方法,当前节点中的线程通过自旋方式尝试获取同步状态。

如果当前节点的前驱节点是头结点,并且重新尝试获取锁成功,就将当前节点设置为头结点!否则判断当前线程是否可以被安全阻塞挂起(根据waitStatus来判断,判断函数shouldParkAfterFailedAcquire),是的话就调用parkAndCheckInterrupt方法里的park方法进行挂起。然后再次进入for循环自旋。

如何判断????


当前节点的前驱节点是waitStatus是SIGNAL则可以挂起,之后由前驱节点进行唤醒;

若前驱节点是CANCEL 则向前遍历直到找到一个是SIGNAL的节点,并将当前节点放置在此节点的后面,不能挂起;

否则尝试将前驱节点的状态设置为SIGNAL,不能挂起

2)独占式释放锁

调用release方法来释放锁。这个方法一般由子类自己重写。

首先,尝试释放tryRelease,如果成功那么就唤醒后继节点    unparkSuccessor里的LockSupport.unpark方法

3)共享式获得锁

调用acquireShared方法,首先尝试获得锁,获得成功就返回;获得失败进入等待队列。直到获取到资源为止才返回。

若当前节点的前驱节点是头结点,则尝试进行加锁;如果加锁成功就调用setHeadAndPropagate,将当前节点设置为头结点,唤醒后继节点,并将之前的头结点从队列中踢出去,等待GC回收;

shared模式下是允许多个线程持有一把锁的,其中tryAcquire的返回值标志了是否允许其他线程继续进入。如果允许的话,需要唤醒队列中等待的线程。其中setHeadAndPropagate方法中的doReleaseShared方法的逻辑很简单,就是唤醒后继线程。

如果获得锁失败,判断当前线程是否可以被安全阻塞挂起,是的话就调用parkAndCheckInterrupt方法里的park方法进行挂起。

tryAcquireShared返回值为正,说明获得锁成功,负数则失败。

4)共享式释放锁

调用releaseShared方法;尝试释放锁,释放成功再次调用doReleaseShared,此方法内部还是调用LockSupport.unpark()方法唤醒后继线程。

5)独占超时获得锁

相比于独占获得锁方式,最重要的区别就是:独占获得锁在自旋获得锁失败之后会挂起,让出CPU时间片,之后再次进入for循环自旋尝试获得锁;而独占超时获得锁在获得锁失败之后,判断当前时间是否超时?若超时,则直接返回不再进行for循环;若没有超时,则将当前线程阻塞指定的超时时间之后再次进行for循环进行CAS自旋获取锁!

调用doAcquireNanos方法。该方法在自旋过程中,当节点的前驱节点为头节点时尝试获取同步状态,如果获取成功则从该方法返回,这个过程和独占式同步获取的过程类似,

但是在同步状态获取失败的处理上有所不同。如果当前线程获取同步状态失败,则首先重新计算超时间隔nanosTimeout,则判断是否超时(nanosTimeout小于等于0表示已经超时),如果没有超时,则使当前线程等待nanosTimeout纳秒(当已到设置的超时时间,该线程会从LockSupport.parkNanos(Object blocker,long nanos)方法返回)。

如果nanosTimeout小于等于spinForTimeoutThreshold(1000纳秒)时,将不会使该线程进行超时等待,而是进入快速的自旋过程。原因在于,非常短的超时等待无法做到十分精确,如果这时再进行超时等待,相反会让nanosTimeout的超时从整体上表现得反而不精确。因此,在超时非常短的场景下,同步器会进入无条件的快速自旋。

详情请参见本博客多线程模块AQS源码分析文章

猜你喜欢

转载自blog.csdn.net/Fly_as_tadpole/article/details/87916308