在了解了AQS独占锁模式以后,接下来再来看看共享锁的实现原理。

原文地址:http://www.jianshu.com/p/1161d33fc1d0

搞清楚AQS独占锁的实现原理之后,再看共享锁的实现原理就会轻松很多。两种锁模式之间很多通用的地方本文只会简单说明一下,就不在赘述了,具体细节可以参考我的上篇文章深入浅出AQS之独占锁模式

一、执行过程概述

获取锁的过程:

  1. 当线程调用acquireShared()申请获取锁资源时,如果成功,则进入临界区。
  2. 当获取锁失败时,则创建一个共享类型的节点并进入一个FIFO等待队列,然后被挂起等待唤醒。
  3. 当队列中的等待线程被唤醒以后就重新尝试获取锁资源,如果成功则唤醒后面还在等待的共享节点并把该唤醒事件传递下去,即会依次唤醒在该节点后面的所有共享节点,然后进入临界区,否则继续挂起等待。

释放锁过程:

  1. 当线程调用releaseShared()进行锁资源释放时,如果释放成功,则唤醒队列中等待的节点,如果有的话。

二、源码深入分析

基于上面所说的共享锁执行流程,我们接下来看下源码实现逻辑:

首先来看下获取锁的方法acquireShared(),如下

   public final void acquireShared(int arg) {
//尝试获取共享锁,返回值小于0表示获取失败
if (tryAcquireShared(arg) < 0)
//执行获取锁失败以后的方法
doAcquireShared(arg);
}

这里tryAcquireShared()方法是留给用户去实现具体的获取锁逻辑的。关于该方法的实现有两点需要特别说明:

一、该方法必须自己检查当前上下文是否支持获取共享锁,如果支持再进行获取。

二、该方法返回值是个重点。其一、由上面的源码片段可以看出返回值小于0表示获取锁失败,需要进入等待队列。其二、如果返回值等于0表示当前线程获取共享锁成功,但它后续的线程是无法继续获取的,也就是不需要把它后面等待的节点唤醒。最后、如果返回值大于0,表示当前线程获取共享锁成功且它后续等待的节点也有可能继续获取共享锁成功,也就是说此时需要把后续节点唤醒让它们去尝试获取共享锁。

有了上面的约定,我们再来看下doAcquireShared方法的实现:

    //参数不多说,就是传给acquireShared()的参数
private void doAcquireShared(int arg) {
//添加等待节点的方法跟独占锁一样,唯一区别就是节点类型变为了共享型,不再赘述
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
//表示前面的节点已经获取到锁,自己会尝试获取锁
if (p == head) {
int r = tryAcquireShared(arg);
//注意上面说的, 等于0表示不用唤醒后继节点,大于0需要
if (r >= 0) {
//这里是重点,获取到锁以后的唤醒操作,后面详细说
setHeadAndPropagate(node, r);
p.next = null;
//如果是因为中断醒来则设置中断标记位
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
//挂起逻辑跟独占锁一样,不再赘述
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
//获取失败的取消逻辑跟独占锁一样,不再赘述
if (failed)
cancelAcquire(node);
}
}

独占锁模式获取成功以后设置头结点然后返回中断状态,结束流程。而共享锁模式获取成功以后,调用了setHeadAndPropagate方法,从方法名就可以看出除了设置新的头结点以外还有一个传递动作,一起看下代码:

    //两个入参,一个是当前成功获取共享锁的节点,一个就是tryAcquireShared方法的返回值,注意上面说的,它可能大于0也可能等于0
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; //记录当前头节点
//设置新的头节点,即把当前获取到锁的节点设置为头节点
//注:这里是获取到锁之后的操作,不需要并发控制
setHead(node);
//这里意思有两种情况是需要执行唤醒操作
//1.propagate > 0 表示调用方指明了后继节点需要被唤醒
//2.头节点后面的节点需要被唤醒(waitStatus<0),不论是老的头结点还是新的头结点
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
//如果当前节点的后继节点是共享类型获取没有后继节点,则进行唤醒
//这里可以理解为除非明确指明不需要唤醒(后继等待节点是独占类型),否则都要唤醒
if (s == null || s.isShared())
//后面详细说
doReleaseShared();
}
} private void setHead(Node node) {
head = node;
node.thread = null;
node.prev = null;
}

最终的唤醒操作也很复杂,专门拿出来分析一下:

注:这个唤醒操作在releaseShare()方法里也会调用。

private void doReleaseShared() {
for (;;) {
//唤醒操作由头结点开始,注意这里的头节点已经是上面新设置的头结点了
//其实就是唤醒上面新获取到共享锁的节点的后继节点
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
//表示后继节点需要被唤醒
if (ws == Node.SIGNAL) {
//这里需要控制并发,因为入口有setHeadAndPropagate跟release两个,避免两次unpark
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue;
//执行唤醒操作
unparkSuccessor(h);
}
//如果后继节点暂时不需要唤醒,则把当前节点状态设置为PROPAGATE确保以后可以传递下去
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue;
}
//如果头结点没有发生变化,表示设置完成,退出循环
//如果头结点发生变化,比如说其他线程获取到了锁,为了使自己的唤醒动作可以传递,必须进行重试
if (h == head)
break;
}
}

接下来看下释放共享锁的过程:

public final boolean releaseShared(int arg) {
//尝试释放共享锁
if (tryReleaseShared(arg)) {
//唤醒过程,详情见上面分析
doReleaseShared();
return true;
}
return false;
}

注:上面的setHeadAndPropagate()方法表示等待队列中的线程成功获取到共享锁,这时候它需要唤醒它后面的共享节点(如果有),但是当通过releaseShared()方法去释放一个共享锁的时候,接下来等待独占锁跟共享锁的线程都可以被唤醒进行尝试获取。

三、总结

跟独占锁相比,共享锁的主要特征在于当一个在等待队列中的共享节点成功获取到锁以后(它获取到的是共享锁),既然是共享,那它必须要依次唤醒后面所有可以跟它一起共享当前锁资源的节点,毫无疑问,这些节点必须也是在等待共享锁(这是大前提,如果等待的是独占锁,那前面已经有一个共享节点获取锁了,它肯定是获取不到的)。当共享锁被释放的时候,可以用读写锁为例进行思考,当一个读锁被释放,此时不论是读锁还是写锁都是可以竞争资源的。

深入浅出AQS之共享锁模式的更多相关文章

  1. 深入浅出AQS之条件队列

    相比于独占锁跟共享锁,AbstractQueuedSynchronizer中的条件队列可能被关注的并不是很多,但它在阻塞队列的实现里起着至关重要的作用,同时如果想全面了解AQS,条件队列也是必须要学习 ...

  2. 深入浅出AQS之组件概览

    之前分析了AQS中的独占锁,共享锁,条件队列三大模块,现在从结构上来看看AQS各个组件的情况. 原文地址:http://www.jianshu.com/p/49b86f9cd7ab 深入浅出AQS之独 ...

  3. AQS 详解之共享锁模式

    概括 AQS框架数据结构是一个先进先出的双向队列,当多个线程进行竞争资源时,那些竞争失败的线程会加入到队列中.他向上层提供了很多接口,其中一个是acquireShared获取共享模式的接口.本文将会根 ...

  4. 并发编程学习笔记(9)----AQS的共享模式源码分析及CountDownLatch使用及原理

    1. AQS共享模式 前面已经说过了AQS的原理及独享模式的源码分析,今天就来学习共享模式下的AQS的几个接口的源码. 首先还是从顶级接口acquireShared()方法入手: public fin ...

  5. 并发编程-深入浅出AQS

    AQS是并发编程中非常重要的概念,它是juc包下的许多并发工具类,如CountdownLatch,CyclicBarrier,Semaphore 和锁, 如ReentrantLock, ReaderW ...

  6. canal源码之BooleanMutex(基于AQS中共享锁实现)

    在看canal源码时发现一个有趣的锁实现--BooleanMutex 这个锁在canal里面多处用到,相当于一个开关,比如系统初始化/授权控制,没权限时阻塞等待,有权限时所有线程都可以快速通过 先看它 ...

  7. 深入浅出AQS之独占锁模式

    每一个Java工程师应该都或多或少了解过AQS,我自己也是前前后后,反反复复研究了很久,看了忘,忘了再看,每次都有不一样的体会.这次趁着写博客,打算重新拿出来系统的研究下它的源码,总结成文章,便于以后 ...

  8. 从零开始了解多线程 之 深入浅出AQS -- 上

    java锁&AQS深入浅出学习--上 上一篇文章中我们一起学习了jvm缓存一致性.多线程间的原子性.有序性.指令重排的相关内容, 这一篇文章便开始和大家一起学习学习AQS(AbstractQu ...

  9. 深入浅出AQS源码解析

    最近一直在研究AQS的源码,希望可以更深刻的理解AQS的实现原理.虽然网上有很多关于AQS的源码分析,但是看完以后感觉还是一知半解.于是,我将自己的整个理解过程记录下来了,希望对大家有所帮助. 基本原 ...

随机推荐

  1. quartus2中FPGA管脚分配保存方法(转)

    一.摘要 将Quartus II中FPGA管脚的分配及保存方法做一个汇总. 二.管脚分配方法 FPGA 的管脚分配,除了在QII软件中,选择“Assignments ->Pin”标签(或者点击按 ...

  2. 转每天一个linux命令(2):cd命令

      Linux cd 命令可以说是Linux中最基本的命令语句,其他的命令语句要进行操作,都是建立在使用 cd 命令上的. 所以,学习Linux 常用命令,首先就要学好 cd 命令的使用方法技巧. 1 ...

  3. SpringMVC + Mybatis bug调试 SQL正确,查数据库却返回NULL

    今天碰到个bug,有点意思 背景是SpringMVC + Mybatis的一个项目,mapper文件里写了一条sql 大概相当于 select a from tableA where b = &quo ...

  4. iOS 微信支付流程详解

    背景 自微信支付.支付宝支付入世以来,移动端的支付日渐火热.虚拟货币有取代实体货币的趋向(这句纯属扯淡,不用管),支付在app开发中是一项基本的功能,有必要去掌握.从难易程度上讲,不管是微信支付还是支 ...

  5. 总结各种排序算法【Java实现】

    一.插入类排序 1.直接插入排序 思想:将第i个插入到前i-1个中的适当位置 时间复杂度:T(n) = O(n²). 空间复杂度:S(n) = O(1). 稳定性:稳定排序. 如果碰见一个和插入元素相 ...

  6. 关于Meta标签中format-detection属性及含义

    一.Meta标签中的format-detection属性及含义 意为:格式检测 或许你会有这样的经历:当你在制作手机端的页面中,点击了没有加任何链接的格式的数字时,这时手机会进行自动拔号提示操作! 禁 ...

  7. mysql 用多次查询代替一次复杂join查询的优点分析

    详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt344 多高性能的应用都会对关联查询进行分解.简单地,可以对每一个表进行一次单 ...

  8. 3_SQL Server通过代码的方式添加数据

    --通过代码添加数据 --第一种方式--insert into 表名(列名1,列名2,...)values (值1,值2,...)insert into Department(DepName, Dep ...

  9. Linux-chown命令(1)

    chown  [chang owner]:更改文件的属主,也就是指定文件的拥有者改为另一个指定的用户或组. 命令格式: chown [选项]... [用户][:[组]] 文件... 例子:  sudo ...

  10. 结对编程1-四则运算GUI实现(58、59)

    题目描述 我们在个人作业1中,用各种语言实现了一个命令行的四则运算小程序.进一步,本次要求把这个程序做成GUI(可以是Windows PC 上的,也可以是Mac.Linux,web,手机上的),成为一 ...