java架构之路(多线程)AQS之ReetrantLock显示锁的使用和底层源码解读
java架构之路(多线程)AQS之ReetrantLock显示锁的使用和底层源码解读
说完了我们的synchronized,这次我们来说说我们的显示锁ReetrantLock。
上期回顾:
上次博客我们主要说了锁的分类,synchronized的使用,和synchronized隐式锁的膨胀升级过程,从无锁是如何一步步升级到我们的重量级锁的,还有我们的逃逸分析。
锁的粗化和锁的消除
这个本来应该是在synchronized里面去说的,忘记了,不是很重要,但是需要知道有这么一个东西啦。
我们先来演示一下锁的粗化:
StringBuffer sb = new StringBuffer();
public void lockCoarseningMethod(){
//jvm的优化,锁的粗化 sb.append("1"); sb.append("2"); sb.append("3"); sb.append("4");
}
我们都知道我们的StringBuffer是线程安全的,也就是说我们的StringBuffer是用synchronized修饰过的。那么我们可以得出我们的4次append都应该是套在一个synchronized里面的。
StringBuffer sb = new StringBuffer();
public void lockCoarseningMethod() {
synchronized (Test.class) { sb.append("1"); } synchronized (Test.class) { sb.append("2"); } synchronized (Test.class) { sb.append("3"); } synchronized (Test.class) { sb.append("4"); }
}
按照理论来说应该是这样的,其实JVM对synchronized做了优化处理,底层会优化成一次的synchronized修饰,感兴趣的可以用javap -c 自己看一下,这里就不带大家去看了,我以前的博客有javap看汇编指令码的过程。
StringBuffer sb = new StringBuffer();
public void lockCoarseningMethod() {
synchronized (Test.class) { sb.append("1"); sb.append("2"); sb.append("3"); sb.append("4"); }
}
再来看一下锁的消除,其实这个锁的消除,真的对于synchronized理解了,锁的消除一眼就知道是什么了。
public static void main(String[] args) {
synchronized (new Object()){ System.out.println("开始处理逻辑"); }
}
对于synchronized而言,我们每次去锁的都是对象,而你每次都创建的一个新对象,那还锁毛线了,每个线程都可以拿到对象,都可以拿到对象锁啊,所以没不会产生锁的效果了。
概述AQS:
AQS是AbstractQueuedSynchronizer的简称,字面意思,抽象队列同步器。Java并发编程核心在于java.concurrent.util包而juc当中的大多数同步器 实现都是围绕着共同的基础行为,比如等待队列、条件队列、独占获取、共享获 取等,而这个行为的抽象就是基于AbstractQueuedSynchronizer简称AQS,AQS定 义了一套多线程访问共享资源的同步器框架,是一个依赖状态(state)的同步器。就是我们上次博客说的什么公平锁,独占锁等等。
AQS具备特性
阻塞等待队列
共享/独占
公平/非公平
可重入
允许中断
AQS的简单原理解读:
ReetrantLock的内部功能还是很强大的,有很多的功能,我们来一点点缕缕。如Lock,Latch,Barrier等,都是基于AQS框架实现,一般通过定义内部类Sync继承AQS将同步器所有调用都映射到Sync对应的方法AQS内部维护属性volatile int state (32位),state表示资源的可用状态
State三种访问方式
getState()
setState()
compareAndSetState()
AQS定义两种资源共享方式
Exclusive-独占,只有一个线程能执行,如ReentrantLock
Share-共享,多个线程可以同时执行,如Semaphore/CountDownLatch
AQS定义两种队列
同步等待队列
条件等待队列
AQS已经在顶层实现好了。自定义同步器实现时主要实现以下几种方法:
isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。
tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。
刚才提到那么多属性,可能会有一些懵,我们来看一下ReentrantLock内部是怎么来实现哪些锁的吧。
打开我们的ReetrantLock源代码可以看到一个关键的属性
private final Sync sync;
后面有一个抽象方法并且继承了AbstractQueuedSynchronizer类,内部有一个用volatile修饰过的整型变量state,他就是用来记录上锁次数的,这样就实现了我们刚才的说的重入锁和非可重入锁。我们来画一个图。
AbstractQueuedSynchronizer这个类里面定义了详细的ReetrantLock的属性,后面我会一点点去说,带着解读一下源码(上面都是摘自源码的)。state和线程exclusiveOwnerThread比较好理解,最后那个队列可能不太好弄,我这里写的也是比较泛化的,后面我会弄一个专题一个个去说。 相面说的CLH队列其实不是很准确,我们可以理解为就是一个泛型为Node的双向链表结构就可以了。
等待队列中Node节点内还有三个很重要的属性就是prev前驱指针指向我们的前一个Node节点,和一个next后继指针来指向我们的下一个Node节点,这样就形成了一个双向链表的结构,于此同时还有一个Thread来记录我们的当前线程。
在条件队列中,prev和next指针都是null的,不管是什么队列,他都有一个waitStatus的属性来记录我们的节点状态的,就是我们刚才说的CANCELLED结束、SIGNAL可唤醒那四个常量值。
AQS中ReetrantLock的使用:
公平锁和非公平锁:这个还是比较好记忆的,举一个栗子,我们去车站排队上车,总有**插队,用蛇形走位可以上车的是吧,这就是一个非公平的锁,如果说,我们在排队的时候加上护栏,每次只能排一个人,他人无法插队的,这时就是一个公平锁。总之就是不加塞的就是公平的,我们都讨厌不公平。
重入锁与非可重入锁:这个也很好理解,重入锁就是当我们的线程A拿到锁以后,可以继续去拿多把锁,然后再陆陆续续的做完任务再去解锁,非可重入呢,就是只能获得一把锁,如果想获取多把锁,不好意思,去后面排下队伍。下面我化了一个重入锁的栗子,快过年了,大家提着行李回老家,我们进去了会一并带着行李进去(不带行李的基本是行李丢了),这就是一个重入锁的栗子,我们人进去了获得通道通过(锁),然后我们也拖着行李获得了通道通过(锁),然后我们才空出通道供后面的人使用。如果是非可重入锁就是人进去就进去吧,行李再次排队,说不准什么时候能进来。
上一段代码来验证一下我们上面说的那些知识点。
import java.util.concurrent.locks.ReentrantLock;
public class Test {
private ReentrantLock lock = new ReentrantLock(true);//true公平锁,false非公平锁
public void lockMethod(String threadName) { lock.lock(); System.out.println(threadName + "得到了一把锁1"); lock.lock(); System.out.println(threadName + "得到了一把锁2"); lock.lock(); System.out.println(threadName + "得到了一把锁3"); lock.unlock(); System.out.println(threadName + "释放了一把锁1"); lock.unlock(); System.out.println(threadName + "释放了一把锁2"); lock.unlock(); System.out.println(threadName + "释放了一把锁3"); }
public static void main(String[] args) { Test test = new Test(); new Thread(() -> { String threadName = Thread.currentThread().getName(); test.lockMethod(threadName); }, "线程A").start(); }
}
通过代码阅读我们知道我们弄一个重入锁,加三次锁,解三次锁,我们来看一下内部sync的变化,调试一下。
我们看到了我们的state变量是用来存储我们的入锁次数的。刚才去看过源码的小伙伴知道了我们的state是通过volatile修饰过的,虽然可以保证我们的有序性和可见性,但是一个int++的操作,他是无法保证原子性的,我们继续来深挖一下代码看看内部是怎么实现高并发场景下保证数据准确的。点击lock方法进去,我们看到lock方法是基于sync来操作的,就是我们上面的画的那个ReetrantLock的图。
/**
- Sync object for fair locks
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L; final void lock() {//开始加锁 acquire(1); } /** * Fair version of tryAcquire. Don't grant access unless * recursive call or no waiters or is first. */ protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread();//得到当前线程 int c = getState();//得到上锁次数 if (c == 0) {//判断是否上过锁 if (!hasQueuedPredecessors() &&//hasQueuedPredecessors判断是否有正在等待的节点, compareAndSetState(0, acquires)) {//通过unsafe去更新上锁次数 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; }
}
这次我们开启多个线程来同时访问来看一下我们的Node的变化。同时开启ABCD四个线程来执行这个
这次我们看到了head属性和tail属性不再是空的。head是也是一个node节点,前驱指针是空的,后驱指针指向后继节点,Thread为空,tail的node节点正好是和head相对应的节点。这样的设计就是为了更好的去验证队列中还是否存在剩余的线程节点需要处理。然后该线程运行结束以后会唤醒在队列中的节点,然其它线程继续运行。
我们知道我们创建的公平锁,如果说BCD好好的在排队,E线程来了,只能好好的去排队,因为公平,所以排队,如果我们创建的是非公平锁,E线程就有机会拿到锁,拿到就运行,拿不到就去排队。
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
上千家企业涌入蚂蚁开放联盟链:在区块链上抢先吃红利螃蟹
近日,蚂蚁区块链的“开放联盟链”在云端开放工具与商家营销工具两大场景开始发力:区块链创业公司“纯白矩阵”用1个月时间,在开放联盟链上开发了ChainIDE全套的云端智能合约服务,1月14日全面开放公测;“幂玛科技”基于开放联盟链打造了首个基于支付宝小程序的区块链营销工具,他们是在开放联盟链上较早吃到螃蟹的公司。自去年11月8日开放邀请制公测以来,开放联盟链已有1000余家客户申请体验。 无论出于业务变革需要还是探索兴趣,如火如荼的区块链技术让无数企业与个人跃跃欲试。但对于大多数中小企业与个人而言,投身区块链的技术难度与高昂成本是一座难以逾越的壁垒。为了让更多中小微企业与个人开发者能低成本、更快速地使用区块链技术,蚂蚁区块链在2019年底正式推出“开放联盟链。” “开放联盟链”是一个低成本、低门槛、开放普惠的区块链服务网络,依赖蚂蚁区块链联盟链强大的技术基础、创新公有许可机制,联合各行业权威节点合作伙伴共同打造。开放联盟链致力于解决现有区块链规模化应用难题,本着生态共建,合作共赢的原则,向广大开发者开放该服务网络,赋能全行业开发者,共建生态繁荣。 生态共建,合作共赢 蚂蚁区块链开放联盟链...
- 下一篇
临近毕业,2020春招困惑你的十大问题,你中招了吗?
通过某知名网站搜集网友分享Android开发面经,很多Android开发者大都遇到了这些问题: 1. 现在安卓的形势,很多人都说不行了,我刚入行没多久是不是要跑路了呀? 2. 我是双非/三本/专科学校的,我有机会进入大厂吗? 3. 非计算机专业的学生能学好吗? 4. 如何学习Android? 5. Android学习该学哪些东西? 6. 我该如何准备Android面试? 7. 面试的简历准备注意事项有哪些呢? 8. 没有项目经历/博客/Github开源项目怎么办? 9. 大厂到底青睐什么样的应届生? 10. 如果面试官问你“你有什么问题问我吗?”时,你该如何回答? 下面我们一一回答这些方面的问题。我会根据自己的一点经验对大部分人关心的这些问题进行答疑解惑。 现在又刚好赶上年末,这篇文章也算是给2020春招准备往Android方向发展的朋友们指明一条学习之路。道理懂了如果没有实际行动,那这篇文章对你或许没有任何意义。 (1)Android开发前景? 我们通过智联招聘、拉勾网、Boss直聘、前程无忧发布的移动互联网招聘岗位需求发现:移动端的招聘量变少,但中高端的职位却多了起来,这说明行业只...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- CentOS8编译安装MySQL8.0.19
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- CentOS8安装Docker,最新的服务器搭配容器使用
- CentOS7,CentOS8安装Elasticsearch6.8.6
- Red5直播服务器,属于Java语言的直播服务器
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- Hadoop3单机部署,实现最简伪集群
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题