java并发编程的艺术(三)---lock源码
本文来源于翁舒航的博客,点击即可跳转原文观看!!!(被转载或者拷贝走的内容可能缺失图片、视频等原文的内容)
若网站将链接屏蔽,可直接拷贝原文链接到地址栏跳转观看,原文链接:https://www.cnblogs.com/wengshuhang/p/10200992.html
jdk1.5以后,并发包中新增了lock接口,
它相对于synchronized,多了以下三个主要特性:尝试非阻塞地获取锁(尝试获取锁成功则持有)、能被中断地获取锁(锁的进程能响应中断)、超时获取锁(指定时间截止之前获取锁)。
我们看看它接口中定义的api:
获取锁
可中断地获取锁
尝试非阻塞地获取锁,能够获取则返回true,否则false
超时获取锁,三种返回情况:1、当前线程在超时时间内获得了锁。2、当前线程在超时时间内被中断。3、超时时间内没获得锁
释放锁
获取等待通知组件,该组件和当前的锁绑定,当前线程只有获得了锁,才能调用该组件的wait()方法,而调用后,当前线程将释放锁。
java中lock的实现是ReentrantLock
观测其源码,我们可以发现,lock接口的所有实现方法均是通过一个AbstractQueuedSynchronizer(同步器)的对象实现的

队列同步器是用来构建锁或者其他同步组件的基础框架,使用了一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作。
他有三个方法改变状态


getState():获取当前同步状态
setState():设置当前同步状态
compareAndSetState(): 使用CAS设置当前状态,保证原子性
同步器中的队列使用了一个双向队列来管理同步状态



每个队列元素都有三种状态: cancelled:1 等待的线程超时或者被中断,将取消等待变成该状态(已取消)
signal: -1 下一个节点处于等待状态,当前节点若是释放了同步状态或者被取消了,将通知后继节点得以运行
condition: -2 节点在等待队列中,等待在condition上,其他线程对condition调用了signal()方法后,该节点将从等待队列中转移到同步队列中,加入到对同步状态的获取中。
propagate: -3 传播共享式同步状态
initial: 0 初始状态
主要对象:
node prev:前驱节点
node next:后继节点
node nextWaiter: 等待队列中的后继节点。
Thread thread: 获取同步状态的线程


同步器中存储着头节点head跟尾节点tail。
同步器中读写锁状态通过32位的整形数据存储,前16位代表读状态,后16位代表写状态。
其中写状态表示state & 0x0000FFFF , 读状态为 state >>> 16
源码中实现lock的方法,是调用了同步器中的acquire(int arg)(独占式同步状态)方法

该方法尝试独占获取同步状态,成功则返回,否则进入同步队列等待



通过死循环来保证节点的正确添加(CAS)

节点进入同步队列后,进入一个自旋的过程,当获得同步状态则调用release从自旋退出,并唤醒头节点的后继节点

独占式同步状态总结: 获取同步状态失败的线程会进入到同步器队列中进行自旋,移除队列的条件是前驱节点为头节点且成功获取了同步状态,在释放同步状态时候,同步器会调用tryRelease()方法释放同步状态并且唤醒后继节点。
共享式同步状态:主要用在读写操作上,多线程可以同时读,但是写锁会排他。
同步器中的acquireShared()方法就是以共享式地获取同步状态

我们可以看到在ReentrantReadWriteLock中的读锁ReadLock中的lock()方法

调用了同步器中的共享式获取同步状态,而写锁writeLock中则调用了独占式方法

重入锁:支持线程对资源的重复加锁,reentrantLock中先判断当前线程是否为获取锁的线程,是则成功获取,锁获取成功时候计数器加1,释放时候减1,当获取n次并释放n次时候,计数器为0则表示当前锁已经释放,源码方法为nonfairTrAcquire()非公平锁Nonfairsync

公平锁的源码 FairSync

两者的比较就是判断条件多了hasQueuedPredecessors()方法,就是加入了同步队列中当前节点是否有前驱节点的判断,如果方法返回true 则表示有线程更早请求获取锁,因此要等待前驱线程获取并释放锁之后才能获取。
为什么会有公平锁跟非公平锁呢:
其实非公平锁(默认)系统线程开销更低,公平锁按照队列fifo的原则,进行了大量的线程切换,而非公平锁,大部分是同一个线程又获取到了锁,进行了少量的线程切换,从而提升了性能。
读写锁中,写锁的获取 需判断读锁的状态是否为0,为了避免其他线程在读,读锁的获取, 只要写锁的16位标志位为0 或者是 当前线程获取了写锁 就可以获取读锁。
锁降级:
锁降级是从写锁降级到读锁,但是在释放写锁之前,需要先获取读锁,否则当写锁释放,当其他进程竞争到写锁,原进程的读锁会获取失败。
LockSupport工具: 用于阻塞进程
Condition 接口 : 监视器方法 实现 等待/通知模式
等待队列是一个FIFO的队列,当线程调用了Condition.await()方法,线程就将释放锁、构造成节点加入等待队列并进行等待状态,一个同步器可以拥有多个等待队列,做法就是lock.newCondition, 唤醒方法是Condition.signal()方法, 则唤醒节点的线程就会开始尝试去获取同步状态

isOnSyncQueued(node)方法判断了该节点是否是等待状态并且是否是等待队列的首节点,是则false跳出循环


signal方法先校验了当前线程是否是获取了锁的进程,接着获取等待队列的首节点,将其移动到同步队列并使用LockSupport唤醒节点中的线程,被唤醒的线程将从await()的while循环中退出进而调用同步器的acquireQueued()方法加入到获取同步状态的竞争中。
Condition是在java 1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition1的await()、signal()这种方式实现线程间协作更加安全和高效。简单说,他的作用是使得某些线程一起等待某个条件(Condition),只有当该条件具备(signal 或者 signalAll方法被调用)时,这些等待线程才会被唤醒,从而重新争夺锁。
java并发编程的艺术(三)---lock源码的更多相关文章
- Java并发编程的艺术(三)——volatile
1. 并发编程的两个关键问题 并发是让多个线程同时执行,若线程之间是独立的,那并发实现起来很简单,各自执行各自的就行:但往往多条线程之间需要共享数据,此时在并发编程过程中就不可避免要考虑两个问题:通信 ...
- Java并发编程笔记之StampedLock锁源码探究
StampedLock是JUC并发包里面JDK1.8版本新增的一个锁,该锁提供了三种模式的读写控制,当调用获取锁的系列函数的时候,会返回一个long 型的变量,该变量被称为戳记(stamp),这个戳记 ...
- 【Java并发编程】16、ReentrantReadWriteLock源码分析
一.前言 在分析了锁框架的其他类之后,下面进入锁框架中最后一个类ReentrantReadWriteLock的分析,它表示可重入读写锁,ReentrantReadWriteLock中包含了两种锁,读锁 ...
- 【Java并发编程】19、DelayQueue源码分析
DelayQueue,带有延迟元素的线程安全队列,当非阻塞从队列中获取元素时,返回最早达到延迟时间的元素,或空(没有元素达到延迟时间).DelayQueue的泛型参数需要实现Delayed接口,Del ...
- java并发编程基础-ReentrantLock及LinkedBlockingQueue源码分析
ReentrantLock是一个较为常用的锁对象.在上次分析的uil开源项目中也多次被用到,下面谈谈其概念和基本使用. 概念 一个可重入的互斥锁定 Lock,它具有与使用 synchronized 相 ...
- Java并发编程笔记之Semaphore信号量源码分析
JUC 中 Semaphore 的使用与原理分析,Semaphore 也是 Java 中的一个同步器,与 CountDownLatch 和 CycleBarrier 不同在于它内部的计数器是递增的,那 ...
- 【Java并发编程】17、SynchronousQueue源码分析
SynchronousQueue是一种特殊的阻塞队列,不同于LinkedBlockingQueue.ArrayBlockingQueue和PriorityBlockingQueue,其内部没有任何容量 ...
- 【Java并发编程】22、Exchanger源码解析(JDK1.7)
Exchanger是双向的数据传输,2个线程在一个同步点,交换数据.先到的线程会等待第二个线程执行exchangeSynchronousQueue,是2个线程之间单向的数据传输,一个put,一个tak ...
- 【Java并发编程】18、PriorityBlockingQueue源码分析
PriorityBlockingQueue是一个基于数组实现的线程安全的无界队列,原理和内部结构跟PriorityQueue基本一样,只是多了个线程安全.javadoc里面提到一句,1:理论上是无界的 ...
- Java并发编程的艺术(六)——线程间的通信
多条线程之间有时需要数据交互,下面介绍五种线程间数据交互的方式,他们的使用场景各有不同. 1. volatile.synchronized关键字 PS:关于volatile的详细介绍请移步至:Java ...
随机推荐
- Ubuntu 16.04下安装Apache压力测试工具ab
安装 sudo apt-get install apache2-utils 简单使用 # 对http://www.baidu.com/进行100次请求,10个并发请求压力测试结果. ab -n 100 ...
- Unity色子的投掷与点数的获得(详解)
前几天需要一个色子的投掷并且获得朝上点数的Unity脚本,在网上找了很多,都是一个模子刻出来的. 对于2018版的我来说,网上找的都是很早就弃用了的老版本. 好不容易能运行了,结果并不理想,于是又突发 ...
- Python小白学习之路(二十)—【打开文件的模式二】【文件的其他操作】
打开文件的模式(二) 对于非文本文件,我们只能使用b模式,"b"表示以字节的方式操作(而所有文件也都是以字节的形式存储的,使用这种模式无需考虑文本文件的字符编码.图片文件的jgp格 ...
- P1631 序列合并
P1631 序列合并 有两个长度都是N的序列A和B,在A和B中各取一个数相加可以得到N^2N2个和,求这N^2N2个和中最小的N个. 对于100%的数据中,满足1<=N<=100000. ...
- PHP基础记录
1. require和require_once的区别 require_once()包涵是绝对路径 include() 和require() :语句包括并运行指定文件. include() 产生一个警告 ...
- sql server数据行号
select ROW_NUMBER() over(order by createTime desc) as RowNum,NoticeContent,CreateTime from PTS_Notic ...
- #!/usr/bin/python和#!/usr/bin/env 的区别
#!/usr/bin/python 通常在一个.py文件开头都会有这个语句 它只在Linux系统下生效,意思是当作为可执行文件运行时调用的解释器的位置上面代码的意思是调用/usr/bin/下的Pyth ...
- LR、HMM、CRF和MaxEnt区别
LR:Logistic 是 Softmax 的特殊形式,多以如果 Softmax 与 MaxEnt 是等价的,则 Logistic 与 MaxEnt 是等价的. HMM模型: 将标注看作马尔可夫链,一 ...
- C#的Lambda表达式嵌套例子
/* *curStatsResult是List<string>类型, *x.GetAllOsVersion()结果是string[]类型, *这里是先使用SelectMany()返回一个结 ...
- 关于gcc编译器中函数不用进行原型声明的解释
经过大量实验和参考网上的说法得出一个结论: gcc编译器中,函数可以不用提前进行原型声明,编译器会把函数调用同时认为是声明.需要注意的是,由于函数调用的时候并没有写明函数返回值,这是gcc把调用当成声 ...