JAVA并发-基于AQS实现自己的显示锁
一、了解什么是AQS
原文链接:http://www.studyshare.cn/blog/details/1131/1
AQS是AbstractQueuedSynchronizer (抽象队列同步器)的简称,java中近一半的显示锁是基于AQS实现的。例如:ReentrantLock(独占锁)、Semaphore(信号量)、ReentrantReadWriteLock(读写锁)、CountDownLatch(并发工具类)、ThreadPoolExecutor(线程池)
AQS原理:

1.采用双向链表的数据结构,当多线程同时竞争锁的时候,第一个线程拿到锁后,后续的线程封装成Node节点依次进入同步队列进行排队等待。
2.AQS内部会采取自旋(死循环)的机制,一直判断头节点是否满足获取锁的条件,当锁被第一个线程释放后,队列中头节点条件满足(检查锁的状态是否为0),然后让头节点获取到锁,并脱离队列,如下图:

AQS原理其实并不复杂,就是采用一个同步队列来存储节点(对线程进行包装,node中有thread,prev,next等),并自旋判断头节点是否拿到锁,拿到锁就从队列中移除,如果有新的线程进入则加到队列的尾部。
java开发工具下载地址及安装教程大全,点这里。 更多技术好文,在这里。
二、AQS采用的设计模式及主要方法
(1)AQS采用的是模板方法模式,对模板方法模式不清楚请参考:https://www.cnblogs.com/stonefeng/p/5743673.html
(2)模板方法
独占式获取
acquire()
acquireInterrutpibly()
tryAcquireNanos()
共享式获取
acquireShared()
acquireSharedInterruptibly()
tryAcquireSharedNanos()
独占式释放
release()
共享式释放
releaseShared()
需要覆盖的流程方法
独占式获取
tryAcquire()
共享式获取
tryAcquireShared()
独占式释放
tryRelease()
共享式释放
tryReleaseShared()
isHeldExclusively() :该方法返回同步状态,需要自己覆盖实现
以上方法是AQS的几个核心方法,在下面我们分析源码的时候会具体解释
三、实现一个案例:继承AQS实现一个自己的独占锁
//实现Lock接口
public class MyReentrantLock implements Lock { //使用state做锁的状态标志,state=1表示获取到锁,state=0表示释放锁,其他线程可以竞争锁
private static class Sync extends AbstractQueuedSynchronizer{ /**
* 尝试获取锁的方法
* 需要自己实现的流程方法
* @param arg
* @return
*/
@Override
protected boolean tryAcquire(int arg) {
//CAS比较内存中的原始值为0,则修改为传入的状态值1,当前线程获取到锁
if(compareAndSetState(0 , arg)){
setExclusiveOwnerThread(Thread.currentThread());//当前线程得到了锁,则将当前得到锁的线程设置为独占线程
return true;
}
return false;
} /**
* 释放锁的方法,需要实现
* @param arg
* @return
*/
@Override
protected boolean tryRelease(int arg) {
if(getState() == 0){//判断状态是否为0,为0则直接抛出不支持操作的异常,增强健壮性的代码
throw new UnsupportedOperationException();
}
setExclusiveOwnerThread(null);//将当前独占线程设置为null
setState(0);//将当前标志锁状态的值设置为0,表示锁已经释放
return true;
} /**
* 是否同步独占,true--已被独占,false--未被独占
* @return
*/
@Override
protected boolean isHeldExclusively() {
return getState() == 1 ;
} Condition newCondition(){
return new ConditionObject();//AQS已经实现Condition,此处只需要直接实例化并使用AQS中的实现即可
}
} private Sync sync = new Sync(); @Override
public void lock() {
sync.acquire(1); //获取锁
} @Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);//获取锁,允许获取过程中有中断
} @Override
public boolean tryLock() {
return sync.tryAcquire(1);
} @Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1,unit.toNanos(time));//获取锁,有超时机制
} @Override
public void unlock() {
sync.release(1) ;//释放锁
} @Override
public Condition newCondition() {
return sync.newCondition();//获取AQS中的Condition实例,用于等待、唤醒操作
}
} 1、显示锁的实现机制是实现Lock接口,使用内部类去继承AQS,为何这样做其实就是java是单继承的,AQS是抽象类,Lock是接口,使用接口更具有扩展性。
2、需要自己覆盖的流程方法
tryAcquire():获取锁
tryRelease():释放锁
isHeldExclusively():是否同步独占,true--已被独占,false--未被独占(根据state状态值判断)
测试类:
public class LockTest {
static MyReentrantLock lock = new MyReentrantLock();
public static void main(String[] args) {
for(int i=0;i<5;i++){
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
try{
System.out.println("获取到锁处理业务逻辑");
Thread.sleep(1000) ;
} catch (InterruptedException e) {
e.printStackTrace();
} finally{
lock.unlock();
System.out.println("释放锁");
}
}
});
threadA.start();
}
}
}
四、深入源码,解读AQS原理
1、多线程并发获取锁,lock.lock(),该方法调用后会执行 sync.acquire(1); 进入这个方法的源码

tryAcquire()就是我们自己覆盖实现的方法,尝试去获取锁,得到则返回true,没得到则返回false,如果返回true,则acquire()则直接执行完毕,不会继续往下执行,如果返回false,说明没有得到锁,则需要将当前线程加入等待队列中。addWaiter()就是加入等待队列的方法,进入源码:

接着看看enq()方法的源码:

以上就成功将一个需要等待的线程封装成节点类后加入等待队列了。如果有更多的线程需要加入,则enq(队列还没有任何节点)方法不需要执行,在addWaiter()中代码实现了直接往尾节点后面继续加入。这里是入队列的代码实现,接着看看出队列的具体实现

该方法实现出队列,看源码:

以上方法就走完了获取锁的流程,执行到这一步,在头节点的线程其实是处于阻塞状态的,它需要等待当前持有锁的那个线程去唤醒它,才会继续执行自己的业务代码
接着看看释放锁的源码实现:

总结:到这里其实整个AQS的流程就已经走完了,AQS是使用双向链表数据机构实现一个入队和出队的操作(先进先出),新增节点挂在尾节点的后面,自旋判断当前节点的前驱节点是否是头节点,如果是头节点并尝试获取锁,获取成功就将头节点和后面的节点脱钩,并处于阻塞状态,等待释放锁的方法去唤醒。
以上代码只讲解了AQS实现了一个基本的显示锁,如果要实现可重入锁或者读写锁,需要加入其它控制和实现其它方法,在此就不讨论,有兴趣可以自己去看可重入锁和读写锁的实现。
java开发工具下载地址及安装教程大全,点这里。
更多技术好文,在这里。
JAVA并发-基于AQS实现自己的显示锁的更多相关文章
- JAVA并发-同步器AQS
什么是AQS aqs全称为AbstractQueuedSynchronizer,它提供了一个FIFO队列,可以看成是一个用来实现同步锁以及其他涉及到同步功能的核心组件,常见的有:ReentrantLo ...
- 深入理解Java并发框架AQS系列(二):AQS框架简介及锁概念
深入理解Java并发框架AQS系列(一):线程 深入理解Java并发框架AQS系列(二):AQS框架简介及锁概念 一.AQS框架简介 AQS诞生于Jdk1.5,在当时低效且功能单一的synchroni ...
- 深入理解Java并发框架AQS系列(一):线程
深入理解Java并发框架AQS系列(一):线程 深入理解Java并发框架AQS系列(二):AQS框架简介及锁概念 一.概述 1.1.前言 重剑无锋,大巧不工 读j.u.c包下的源码,永远无法绕开的经典 ...
- 深入理解Java并发框架AQS系列(四):共享锁(Shared Lock)
深入理解Java并发框架AQS系列(一):线程 深入理解Java并发框架AQS系列(二):AQS框架简介及锁概念 深入理解Java并发框架AQS系列(三):独占锁(Exclusive Lock) 深入 ...
- Java并发编程--AQS
概述 抽象队列同步器(AbstractQueuedSynchronizer,简称AQS)是用来构建锁或者其他同步组件的基础框架,它使用一个整型的volatile变量(命名为state)来维护同步状态, ...
- JAVA并发(1)-AQS(亿点细节)
AQS(AbstractQueuedSynchronizer), 可以说的夸张点,并发包中的几乎所有类都是基于AQS的. 一起揭开AQS的面纱 1. 介绍 为依赖 FIFO阻塞队列 的阻塞锁和相关同步 ...
- 深入理解Java并发类——AQS
目录 什么是AQS 为什么需要AQS AQS的核心思想 AQS的内部数据和方法 如何利用AQS实现同步结构 ReentrantLock对AQS的利用 尝试获取锁 获取锁失败,排队竞争 参考 什么是AQ ...
- 【转】Java并发的AQS原理详解
申明:此篇文章转载自:https://juejin.im/post/5c11d6376fb9a049e82b6253写的真的很棒,感谢老钱的分享. 打通 Java 任督二脉 —— 并发数据结构的基石 ...
- java并发:AQS的简单理解
简介: AQS全称 AbstractQueuedSynchronizer,提供了一个基于FIFO(先进先出)队列,可以用于构建锁或者其他相关同步装置的基础框架. ReentrantLock.Semap ...
随机推荐
- windows7 64下redis安装
1.redis官方下载地址:https://redis.io/download redis 64位下载地址:https://github.com/ServiceStack/redis-windows, ...
- python中使用if __name__ == '__main__':
引子 在python中,假设在一个test1.py的模块中定义了一个foo函数,然后调用函数foo进行测试的时候会产生一个内存空间.当你把这个模块导入到test2.py模块中,接下来如果在test2. ...
- The Best Books on Game Dev
https://www.goodreads.com/list/show/99288.The_Best_Books_on_Game_Dev
- zabbix和iptables的nat表结合使用
A 机器要去访问C机器,但是无法直接访问到A可以访问到B机器,B机器可以访问到C机器这时候就可以再B机器设置nat,让A机器访问C机器 正好工作中zabbix server要监控2个http地址,缺无 ...
- 【转】Syncthing – 数据同步利器---自己的网盘,详细安装配置指南,内网使用,发现服务器配置
Syncthing – 数据同步利器---自己的网盘,详细安装配置指南,内网使用,发现服务器配置 原贴:https://www.cnblogs.com/jackadam/p/8568833.html ...
- 【Oracle】ORA-14400: 插入的分区关键字未映射到任何分区
问题描述: 工作中使用kettle将原始库中的数据抽取到标准库中,在抽取过程中报错:[ORA-14400: 插入的分区关键字未映射到任何分区]/[ORA-14400: inserted partiti ...
- 浅谈C#在网络波动时防重复提交
前几天,公司数据库出现了两条相同的数据,而且时间相同(毫秒也相同).排查原因,发现是网络波动造成了重复提交. 由于网络波动而重复提交的例子也比较多: 网络上,防重复提交的方法也很多,使用redis锁, ...
- 关于Rabbitmq的routingkey的作用
对于消息发布者而言它只负责把消息发布出去,甚至它也不知道消息是发到哪个queue,消息通过exchange到达queue,exchange的职责非常简单,就是一边接收发布者的消息一边把这些消息推到qu ...
- @JSONField使用
参考博客:http://www.cnblogs.com/yucy/p/9057049.html 1.经常遇到这种情况,传过来json的key名和我们javaBean的属性名不一样,导致接收不到: Ch ...
- Linux常见企业面试题
1:只查看test.txt (100行)文件中第20行到30行的数据(企业常见面试题) 答: seq (序列) 第一种方法:head -30 test1.txt | tail -11 第二种方法:se ...