Java AQS锁实现原理(java lock aqs)

  本篇文章为你整理了Java AQS锁实现原理(java lock aqs)的详细内容,包含有java锁的实现原理 java lock aqs java锁cas java锁实现方式 Java AQS锁实现原理,希望能帮助你了解 Java AQS锁实现原理。

  ​
 

  

  

  首先阅读一下类的源码注释,可以知道,这几个接口是最关键的。

  ​
 

  

  这几个方法是使用AQS类的关键,只有这几个方法是可以定制的,其他方法几乎都是final的,不可修改。

  从代码实现上看,能看到的变量几乎都是volatile的,能看到的方法几乎都是CAS或者Unsafe类的原子方法。

  

  接下来我们来整理一下这个类的字段和方法,这里我们主要关注private字段和public方法。

  volatile int state:同步状态。

  volatile Node head:等待队列的头,延迟初始化。

  volatile Node tail:等待队列的尾部。初始化后,仅通过casTail修改。

  

  获取锁:

  acquire(int arg)

  acquireShared(int arg)

  

  释放锁:

  release(int arg)

  releaseShared(int arg)

  

  等待队列:

  hasQueuedThreads()

  getFirstQueuedThread()

  isQueued(Thread thread)

  getQueuedThreads()

  getExclusiveQueuedThreads()

  getSharedQueuedThreads()

  

  条件对象ConditionObject:

  await()

  signal()

  

  AQS整体结构,包括加锁/释放锁,条件对象await/signal。

  ​
 

  

  数据结构

  锁的数据结构

  

  
 

  

  ​

  

  一个状态字段state表示同步状态,0表示没有锁竞争,1表示有锁竞争。

  head跟tail分别是等待队列的头节点和尾节点,该等待队列是用双向链表实现的。

  

  

  

  

  

  节点的数据结构

  
 

  

  

  prev:前驱节点

  next:后继节点

  waiter:等待锁的线程

  status:节点状态

  

  节点

  

  节点状态其实有4种:

  取消状态:status

  休眠状态:status=0;

  等待状态:status=1;

  条件等待:status=2;

  

  功能实现

  
独占锁acquire(int arg)

  以独占模式获取,忽略中断。通过调用至少一次{@link#tryAcquire}来实现,并在成功时返回。否则线程将排队,可能会重复阻塞和取消阻塞,调用{@link#tryAcquire},直到成功。此方法可用于实现方法{@link Lock#Lock}。

  ​
 

  

  独占锁实现过程

  

  

  

  

  1.tryAcquire

  尝试以独占模式获取。此方法应查询对象的状态是否允许以独占模式获取对象,如果允许,则应获取对象。

  执行acquire的线程总是调用此方法。如果此方法报告失败,acquire方法可能会将线程排队(如果尚未排队),直到其他线程发出释放信号。这可以用来实现方法{@link Lock#tryLock()}。

  这是一个protected方法,在ReentrantLock里实现了FairSync和NonfairSync,也就是公平锁和非公平锁。

  1-1.FairSyncinReentrantLock

  
 

  

  

  判断队列中有没有其他线程在等待锁,或者当前线程是第一个在等待锁的线程,也就是等待队列中第一个线程,然后CAS尝试修改锁状态,设置当前线程为锁拥有者。

  

  

  

  1-2.NonfairSyncin ReentrantLock

  

  ​
 

  

  不去判断当前线程是否是等待队列中的第一个线程,直接CAS尝试获取锁。

  

  

  2.acquire

  在tryAcquire获取锁失败后,acquire主要做的其实是将当前线程放入等待队列中,然后在循环中判断是否可以参与锁争抢。

  由于等待队列优先调度第一个节点进行锁争抢,这里要根据当前线程是否是等待队列中的第一个节点分情况讨论:

  当前线程在等待队列中不存在;

  当前线程是等待队列中第一个节点;

  当前线程不是等待队列中第一个节点;

  

  

  

  ​
 

  

  

  如果当前节点已存在,且不是头节点,清理队列中无效节点,并且节点继续等待;

  如果当前节点已存在,且是头节点,尝试再次CAS加锁;如果加锁成功,并且是共享锁,唤醒后继节点参与锁争抢;如果加锁失败,该节点进入休眠状态,设置一个短暂的休眠期,休眠期内无法被唤醒参与锁争抢;

  如果当前节点不存在,创建节点,放入等待队列尾部,进行排队等待锁争抢。

  如果被中断,或者在超时时间内没有获取到锁,则加锁失败,从等待队列中清除。

  

  

  

  

  

  共享锁acquireShared(int arg)

  在共享模式下获取,忽略中断。通过至少调用一次{@link#tryAcquireShared}来实现,并在成功时返回。否则线程将排队,可能会重复阻塞和取消阻塞,调用{@link#tryAcquireShared},直到成功。

  

  共享锁跟独占锁最大的区别在于,共享锁可以设置并发数。共享锁status表示可以同时争抢锁的线程数,也就是并发数 1,独占锁并发数=1。

  ​

  

  共享锁实现过程

  
 

  

  

  

  

  1.tryAcquireShared

  这是一个protected方法,在Semaphore里实现了FairSync和NonfairSync,也就是公平锁和非公平锁。

  1-1.FairSyncinSemaphore

  

  

  如果有前驱节点在等待,返回失败;

  如果剩余的并发数 0,返回失败;

  

  1-2.NonfairSyncinSemaphore

  

  ​
 

  ​

  

  不去判断是否有前驱节点在等待,直接根据剩余的并发数来判断。

  

  

  2.acquire

  在独占锁中分析过,这里不再赘述。不同点在于共享锁允许多个线程获取锁。

  

  其实到这里锁的基本功能已经差不多了,如果只是实现线程之间的互斥,那么只需要用到加锁/释放锁就够了。但是如果在互斥的基础上还要进行线程间的协作,就要用到条件对象了。

  

  条件对象ConditionObject

  数据结构

  条件对象的主要数据结构就是一个条件队列,用于存在调用await方法释放锁后的线程。

  ​
 

  

  

  功能实现

  await方法说明:

  将当前线程放入条件队列,然后释放锁,唤醒等待队列中的线程参与加锁。

  
 

  

  

  

  

  signal方法说明:

  将等待时间最长的线程(如果存在)从该条件的等待队列移动到拥有锁的等待队列。

  
 

  最后,我想说的是,AQS的实现思想并不仅仅局限于在读写锁中使用,在很多Java中间件、JVM以及操作系统中都有运用,包括其他语言中也有运用。因为这是一个通用的问题,大家都会遇到并且需要解决。我们日常业务开发中也会遇到,只不过需要使用分布式版本的解决方案。

  

  
 

  以上就是Java AQS锁实现原理(java lock aqs)的详细内容,想要了解更多 Java AQS锁实现原理的内容,请持续关注盛行IT软件开发工作室。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: