Java 并发编程—有锁互斥机制及AQS理论
原文作者:Java并发编程
原文地址:AQS这样学就很简单了
目录
一、有锁互斥机制
二、AQS如何实现互斥
三、结语
如果你是道格李,你要实现一套机制来保证线程互斥,你会如何实现呢?你肯定不会一上来就写代码对吧,你会想有哪些场景会出现线程互斥、针对每个场景抽象出需要实现的功能、针对这些功能底层选择什么样的数据结构什么算法……这其实是一种非常好的学习方法,回归到问题本身去思考问题,然后带着问题去找答案,而不是一上来就一头扎进代码中。
一、有锁互斥机制
1、核心功能
如果你要实现一套机制保证线程互斥,核心是实现这六个功能:抢锁、释放锁、入队、出队、阻塞、唤醒。下面分别说下这六个功能如何实现以及在实现的时候需要考虑哪些特殊情况。
2、抢锁
如果当前没有任何线程持有锁,那进来的线程就可以去抢锁。因为存在多个线程同时上锁,所以需要通过机制来保证同一时刻只能有一个线程能够操作锁标志位,这个机制就是CAS。如果有线程持有了锁,这时候进来的线程如果是持有锁的线程,就发生了重入,需要记录重入次数,因为解锁的时候也要解锁相应的次数;如果不是持有锁的线程,那有两个选择:让线程运行结束、让线程阻塞等其他线程用完锁再唤醒运行。显然,让线程运行结束不合适,因为线程该执行的任务还未执行。正确的做法肯定是先把这个线程保存下来,然后让线程阻塞。待持有锁的线程运行结束再唤醒执行。这里就是公平锁与非公平锁的唯一差异所在,一个临界点。
3、释放锁
持有锁的线程将该执行的任务执行完就得释放锁。当然肯定不是这么简单,还得考虑锁重入、线程唤醒。
4、入队
未抢到锁且不是重入的线程就需要被阻塞等待唤醒,那唤醒的时候去哪找这个线程呢?很明显这些线程满足先阻塞先唤醒原则,与数据结构队列的FIFO特性相吻合。所以在实现的时候需要借助队列,将这部分线程封装成节点入队。入队就得考虑从头部入队还是从尾部入队。如果队列中还没有数据,队列的头尾两个节点都指向这个节点。如果队列中有数据,就从尾部插入。大家在看AQS源码的时候会发现AQS不是这样做的,它会多一个节点。AQS队列的头结点永远是当前持有锁的线程占用,为什么要这样做呢?这就需要对CPU内部结构及工作机制有深入理解,感兴趣的同学可以深入学习下:CLHLock、MCSLock。
5、出队
线程对应的节点何时出队呢?肯定是唤醒以后。那由谁来操作出队呢?是唤醒你的线程还是你自己?可想而知,由自己来操作更合理。那自己什么时候操作出队呢?肯定是唤醒以后。只有理解了这个逻辑,你才能看得懂AQS中阻塞那块的代码为什么那样写。正常的情况下出队都是从头部出,但是有特殊情况会移除队列中部的节点或尾部节点。
6、阻塞
线程如果没有抢到锁依然在那尝试抢锁这就是所谓的自旋锁,很显然,这样很浪费资源,肯定没有抢到锁的线程执行完任务唤醒高效。那如何不占用资源呢?就是阻塞自己,让出资源,不被调度。
7、唤醒
未抢到锁的线程为了不占用资源阻塞了自己,拿到锁的线程执行完任务需要来唤醒,不然就会出现奇怪的现象:抢到锁的线程执行完任务退出了,未抢到锁的线程全部阻塞在那里等待唤醒。
二、AQS如何实现互斥
别看AQS代码挺多,其实搞懂这三个机制,代码理解起来就非常easy。
1、state
这个属性就是锁标志位。如果有线程抢到了锁,这个值就会通过CAS改成1。何时改回来呢?持有锁的线程执行完任务释放锁的时候。
2、waitStatus
这个属性有五个值:0,-1,-2,-3,1。这里介绍其中三个。其他的值跟互斥没关系,我会在讲到相关的技术时讲到。这几个值直接的转换后面会写文章细致,本篇文章大致讲下。未抢到锁的线程被封装成Node插入队列,这个属性默认值为0。插入队列后会进行两次自旋,如果都没抢到锁,就会将它的前置节点的waitStatus改为-1。注意一下这里,改的不是自己的waitStatus,而是它前置节点的。为什么要这样做呢?这就是自旋锁算法CLH的理论。相当于在它的前置节点上设置了一个闹钟,这样在唤醒的时候就不需要去队列取数据,直接判断自己的该属性就可以了。如果是队列中的第二个节点但是抢锁失败了,这个时候就将自己的waitStatus设置为1。这样的线程就得不到调度机会了,会被其他线程从队列中移除。
3、队列
如果想看懂AQS源码,必须对队列的相关操作算法非常熟悉,比如初始化、判空、入队、出队……建议在看AQS源码之前自己用Java实现一遍队列。
三、结语
这篇文章虽然没有讲任何AQS的源码,但是如果你真的看懂了这篇文章,你去读读AQS的源码,你会发现读起来非常轻松。推荐阅读Java并发编程—AQS原理分析
Java 并发编程—有锁互斥机制及AQS理论相关推荐
- Java并发编程—无锁互斥机制及CAS原理
目录 一.CAS简介 二.AtomicInteger代码演示 三.CAS 实现 四.弊端 一.CAS简介 在计算机科学中,比较和交换(Conmpare And Swap)是用于实现多线程同步的原子指令 ...
- Java并发编程之锁机制之LockSupport工具
关于文章涉及到的jdk源码,这里把最新的jdk源码分享给大家----->jdk源码 前言 在上篇文章<Java并发编程之锁机制之AQS(AbstractQueuedSynchronizer ...
- Java并发编程实战之互斥锁
文章目录 Java并发编程实战之互斥锁 如何解决原子性问题? 锁模型 Java synchronized 关键字 Java synchronized 关键字 只能解决原子性问题? 如何正确使用Java ...
- Java并发编程-无锁CAS与Unsafe类及其并发包Atomic
[版权申明]未经博主同意,谢绝转载!(请尊重原创,博主保留追究权) http://blog.csdn.net/javazejian/article/details/72772470 出自[zejian ...
- 【Java 并发编程】线程池机制 ( ThreadPoolExecutor 线程池构造参数分析 | 核心线程数 | 最大线程数 | 非核心线程存活时间 | 任务阻塞队列 )
文章目录 前言 一.ThreadPoolExecutor 构造参数 二.newCachedThreadPool 参数分析 三.newFixedThreadPool 参数分析 四.newSingleTh ...
- 【Java 并发编程】线程池机制 ( 线程池示例 | newCachedThreadPool | newFixedThreadPool | newSingleThreadExecutor )
文章目录 前言 一.线程池示例 二.newCachedThreadPool 线程池示例 三.newFixedThreadPool 线程池示例 三.newSingleThreadExecutor 线程池 ...
- Java并发编程(06):Lock机制下API用法详解
本文源码:GitHub·点这里 || GitEE·点这里 一.Lock体系结构 1.基础接口简介 Lock加锁相关结构中涉及两个使用广泛的基础API:ReentrantLock类和Condition接 ...
- java并发锁有哪些,Java并发编程-公平锁与非公平锁
写这个文章的时候让我想起了让子弹飞的一个台词 公平,公平,还是tmd公平! 什么是公平和非公平 首先,我们来看下什么是公平锁和非公平锁. 公平锁指的是按照线程请求的顺序,来分配锁: 非公平锁指的是不完 ...
- 【java】java 并发编程 StampedLock 锁 【不重要】
文章目录 1.概述 2.synchronized 3.读写锁 3.1 读写锁缺点 4.StampedLock 4.1 缺点 5.案例 5.1 案例1 5.1 案例2 6.性能对比 7.总结 1.概述 ...
最新文章
- VS2010安装项目的系统必备中添加.NET 2.0
- python输入三角形三边处理成三个实数_Python之路:(三)数据处理
- mybatis如何根据mapper接口生成其实现类
- 团队行为心理学读书笔记(8)绩效考核背后的行为心理学
- 扫描PDF417崩溃的原因找到:手机摄像头分辨率低
- .NET Core实战项目之CMS 第四章 入门篇-Git的快速入门及实战演练
- 【每日算法Day 96】腾讯面试题:合并两个有序数组
- 线程的四种实现方式,一些方法及线程的同步
- 网页自动加拼音html,HTML5:给汉字加拼音?让我秀给你看
- CAD格式交换全能:CAD DLL 15.0 Crack
- 教授专栏13 | 陈雷:数据科学助力AI落地
- 解决idea里面Tomcat无论怎么重新启动总是启动原先的项目问题
- ”高内聚 ,低耦合“到底是什么意思?
- 源码编译安装go,ERROR: Cannot find /root/go1.4/bin/go
- ubuntu 下实现 quagga镜像
- 计算机在开机后显示器在显示桌面,电脑开机后显示屏一片空白
- thinkphp配置文件设置session有效期未生效的问题
- 不掉帧不卡顿的游戏直播画面看起来爽爆了,求网络推流搭建方案
- Python——pandas模块—Series数据结构
- 在ABAQUS中如何使用修正DPC帽盖模型