AQS(AbstractQueuedSynchronizer)是JDK1.5提供的一个用来构建锁和同步工具的框架,子类包括常用的ReentrantLock、CountDownLatch、Semaphore等。

  AQS没有锁之类的概念,它有个state变量,是个int类型 ,state 是同步状态位,具体是否能够获取锁就是通过修改state来实现

  AQS的功能可以分为独占和共享,ReentrantLock实现了独占功能

ReentrantLock锁的架构:

  ReentrantLock的内部类Sync继承了AQS,分为公平锁FairSync和非公平锁NonfairSync。

获取锁的过程:

  线程去竞争一个锁,可能成功也可能失败。成功就直接持有资源,不需要进入队列;失败的话进入队列阻塞,等待唤醒后再尝试竞争锁。

公平锁尝试获取锁:

 final void lock() { acquire(1);}

 public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
} protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}

  第一个if判断AQS的state是否等于0,表示锁没有人占有。

  接着,hasQueuedPredecessors判断队列是否有排在前面的线程在等待锁,没有的话调用compareAndSetState使用cas的方式修改state,将0改为1。

  最后线程获取锁成功,setExclusiveOwnerThread将线程记录为独占锁的线程。

  第二个if判断当前线程是否为独占锁的线程,因为ReentrantLock是可重入的,线程可以不停地lock来增加state的值,对应地需要unlock来解锁,直到state为零。

  如果最后获取锁失败,下一步需要将线程加入到等待队列。

线程进入等待队列:

  AQS内部有一条双向队列存放等待线程,节点是Node对象。每个Node维护了线程、前后Node的指针和等待状态等参数。

  线程在加入队列之前,需要包装进Node,调用方法是addWaiter。

  每个Node需要标记是独占的还是共享的,由传入的mode决定,ReentrantLock自然是使用独占模式Node.EXCLUSIVE。

  创建好Node后,如果队列不为空,使用cas的方式将Node加入到队列尾。注意,这里只执行了一次修改操作,并且可能因为并发的原因失败。因此修改失败的情况和队列为空的情况,需要进入enq()方法。

阻塞等待线程:

  线程加入队列后,下一步是调用acquireQueued阻塞线程。

非公平锁获取锁:

 final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}

  在NonfairSync的lock方法里,第一步直接尝试将state修改为1,很明显,这是抢先获取锁的过程。如果修改state失败,则和公平锁一样,调用acquire。

  公平锁会关注队列里排队的情况,老老实实按照FIFO的次序;非公平锁只要有机会就抢占,才不管排队的事。

羊群效应:

  当有多个线程去竞争同一个锁的时候,假设锁被某个线程占用,那么如果有成千上万个线程在等待锁,有一种做法是同时唤醒这成千上万个线程去去竞争锁,这个时候就发生了羊群效应,海量的竞争必然造成资源的剧增和浪费,因此终究只能有一个线程竞争成功,其他线程还是要老老实实的回去等待。

  AQS的FIFO的等待队列给解决在锁竞争方面的羊群效应问题提供了一个思路:保持一个FIFO队列,队列每个节点只关心其前一个节点的状态,线程唤醒也只唤醒队头等待线程。其实这个思路已经被应用到了分布式锁的实践中,见:Zookeeper分布式锁的改进实现方案。

ReentrantLock和AQS的更多相关文章

  1. ReentrantLock 与 AQS 源码分析

    ReentrantLock 与 AQS 源码分析 1. 基本结构    重入锁 ReetrantLock,JDK 1.5新增的类,作用与synchronized关键字相当,但比synchronized ...

  2. 多线程学习笔记三之ReentrantLock与AQS实现分析

    目录 简介 AQS同步状态 AQS同步队列 ReentrantLock数据结构 公平锁的获取 tryAcquire(arg) addWaiter(Node mode) acquireQueued(fi ...

  3. 透过 ReentrantLock 分析 AQS 的实现原理

    对于 Java 开发者来说,都会碰到多线程访问公共资源的情况,这时候,往往都是通过加锁来保证访问资源结果的正确性.在 java 中通常采用下面两种方式来解决加锁得问题: synchronized 关键 ...

  4. 面经手册 · 第17篇《码农会锁,ReentrantLock之AQS原理分析和实践使用》

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 如果你相信你做什么都能成,你会自信的多! 千万不要总自我否定,尤其是职场的打工人.如 ...

  5. 从ReentrantLock看AQS (AbstractQueuedSynchronizer) 运行流程

    从ReentrantLock看AQS (AbstractQueuedSynchronizer) 运行流程 概述 本文将以ReentrantLock为例来讲解AbstractQueuedSynchron ...

  6. 基于ReentrantLock的AQS的源码分析(独占、非中断、不超时部分)

    刚刚看完了并发实践这本书,算是理论具备了,看到了AQS的介绍,再看看源码,发现要想把并发理解透还是很难得,花了几个小时细分析了一下把可能出现的场景尽可能的往代码中去套,还是有些收获,但是真的很费脑,还 ...

  7. 扒一扒ReentrantLock以及AQS实现原理

    提到JAVA加锁,我们通常会想到synchronized关键字或者是Java Concurrent Util(后面简称JCU)包下面的Lock,今天就来扒一扒Lock是如何实现的,比如我们可以先提出一 ...

  8. ReentrantLock 以及 AQS 实现原理

    什么是可重入锁?       ReentrantLock是可重入锁,什么是可重入锁呢?可重入锁就是当前持有该锁的线程能够多次获取该锁,无需等待.可重入锁是如何实现的呢?这要从ReentrantLock ...

  9. Java并发(5)- ReentrantLock与AQS

    引言 在synchronized未优化之前,我们在编码中使用最多的同步工具类应该是ReentrantLock类,ReentrantLock拥有优化后synchronized关键字的性能,又提供了更多的 ...

随机推荐

  1. Java学习之基本数据类型和引用数据类型区别

    JAVA中分为基本数据类型和引用数据类型区别一.基本数据类型: byte:Java中最小的数据类型,在内存中占8位(bit),即1个字节,取值范围-128~127,默认值0 short:短整型,在内存 ...

  2. 上传文件到aws的s3存储

    只要有aws-cli客户端就可以上传文件到aws的S3存储.可以在任意机器上.这里以centos为例. 1.安装python.pip. # yum install -y python python-p ...

  3. JavaScript学习复习

    JavaScript 输出 使用 window.alert() 弹出警告框. 使用 document.write() 方法将内容写到 HTML 文档中. 使用 innerHTML 写入到 HTML 元 ...

  4. IIS并发连接数及性能优化

    如果要查看IIS连接数,最简单方便的方法是通过“网站统计”来查看,“网站统计”的当前在线人数可以认为是当前IIS连接数.然而,“网站统计”的当前在线人数统计时间较长,一般为10分钟或15分钟,再加上统 ...

  5. 「Vue」JS方法学习

    1.构造函数 大写开头的,能被NEW一个新实例,实例即执行回调函数 异步返回数据.then指定回调函数的时候,成功的回调函数必须传,失败的回调可以不传 var fs = require('fs') f ...

  6. oracle connect by用法篇 (转)

    1.基本语法 select * from table [start with condition1] connect by [prior] id=parentid 1 2 1 2 一般用来查找存在父子 ...

  7. spring JMS在接收消息的时候总是报错

    spring JMS在接收消息的时候总是报错 org.springframework.jms.UncategorizedJmsException: Uncategorized exception oc ...

  8. java注解 @SuppressWarnings注解用法

    @SuppressWarnings注解用法 @SuppressWarnings注解主要用在取消一些编译器产生的警告对代码工具左侧行列提示,但这种警告可以通过注释类型声明来取消 @SuppressWar ...

  9. HTML5页面开发的基础性模板

    分享一个HTML5页面开发的基础性模板,包含了两个版本: 开发版本 注释版本 开发版本 <!DOCTYPE html> <html> <head> <meta ...

  10. javascript类式继承函数最优版

    直接上代码: klass函数 var klass = function (Parent, props) { var Child, F, i; //1.新构造函数 Child = function () ...