多线程(四) AQS底层原理分析
J.U.C 简介
Lock

Lock接口
- void lock() // 如果锁可用就获得锁,如果锁不可用就阻塞直到锁释放
- void lockInterruptibly() // 和lock()方法相似, 但阻塞的线程 可 中 断 , 抛 出java.lang.InterruptedException 异常
- boolean tryLock() // 非阻塞获取锁;尝试获取锁,如果成功返回 true
- boolean tryLock(long timeout, TimeUnit timeUnit) //带有超时时间的获取锁方法
- void unlock() // 释放锁
- public class ReentrantDemo{
- public synchronized void demo(){
- System.out.println("begin:demo");
- demo2();
- }
- public void demo2(){
- System.out.println("begin:demo1");
- synchronized (this){
- }
- }
- public static void main(String[] args) {
- ReentrantDemo rd=new ReentrantDemo();
- new Thread(rd::demo).start();
- } }
- ReentrantLock 的使用案例
- public class AtomicDemo {
- private static int count=0;
- static Lock lock=new ReentrantLock();
- public static void inc(){
- lock.lock();
- try {
- Thread.sleep(1);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- count++;
- lock.unlock();
- }
- public static void main(String[] args) throws
- InterruptedException {
- for(int i=0;i<1000;i++){
- new Thread(()->{AtomicDemo.inc();}).start();;
- }
- Thread.sleep(3000);
- System.out.println("result:"+count);
- }
- }
- package com.lf.threaddemo;
- import java.util.HashMap;
- import java.util.Map;
- import java.util.concurrent.locks.Lock;
- import java.util.concurrent.locks.ReentrantReadWriteLock;
- public class LockDemo {
- static Map<String, Object> cacheMap = new HashMap<>();
- static ReentrantReadWriteLock rwl = new
- ReentrantReadWriteLock();
- static Lock read = rwl.readLock();
- static Lock write = rwl.writeLock();
- public static final Object get(String key) {
- System.out.println("开始读取数据");
- read.lock(); //读锁
- try {
- return cacheMap.get(key);
- } finally {
- read.unlock();
- }
- }
- public static final Object put(String key, Object value) {
- write.lock();
- System.out.println("开始写数据");
- try {
- return cacheMap.put(key, value);
- } finally {
- write.unlock();
- }
- }
- }
StampedLock
StampedLock 支持三种模式,分别是:写锁、悲观读锁和乐观读。其中,写锁、悲观读锁的语义和 ReadWriteLock 的写锁、读锁的语义非常类似,
允许多个线程同时获取悲观读锁,但是只允许一个线程获取写锁,写锁和悲观读锁是互斥的。不同的是:StampedLock 里的写锁和悲观读锁加锁成功之后,
都会返回一个 stamp;然后解锁的时候,需要传入这个 stamp。相关的示例代码如下。
- final StampedLock sl = new StampedLock();
- // 获取/释放悲观读锁示意代码
- long stamp = sl.readLock();
- try {
- //省略业务相关代码
- } finally {
- sl.unlockRead(stamp);
- }
- // 获取/释放写锁示意代码
- long stamp = sl.writeLock();
- try {
- //省略业务相关代码
- } finally {
- sl.unlockWrite(stamp);
- }
StampedLock 的性能之所以比 ReadWriteLock 还要好,其关键是 StampedLock 支持乐观读的方式。
ReadWriteLock 支持多个线程同时读,但是当多个线程同时读的时候,
所有的写操作会被阻塞;而 StampedLock 提供的乐观读,是允许一个线程获取写锁的,也就是说不是所有的写操作都被阻塞。
注意这里,我们用的是“乐观读”这个词,而不是“乐观读锁”,是要提醒你,乐观读这个操作是无锁的,所以相比较 ReadWriteLock 的读锁,乐观读的性能更好一些。
StampedLock 使用注意事项对于读多写少的场景 StampedLock 性能很好,简单的应用场景基本上可以替代 ReadWriteLock,
但是 StampedLock 的功能仅仅是 ReadWriteLock 的子集,在使用的时候,还是有几个地方需要注意一下。
StampedLock 在命名上并没有增加 Reentrant,想必你已经猜测到 StampedLock 应该是不可重入的。
事实上,的确是这样的,StampedLock 不支持重入。这个是在使用中必须要特别注意的。
另外,StampedLock 的悲观读锁、写锁都不支持条件变量,这个也需要你注意。还有一点需要特别注意,
那就是:如果线程阻塞在 StampedLock 的 readLock() 或者 writeLock() 上时,此时调用该阻塞线程的 interrupt() 方法,会导致 CPU 飙升。
例如下面的代码中,线程 T1 获取写锁之后将自己阻塞,线程 T2 尝试获取悲观读锁,也会阻塞;
如果此时调用线程 T2 的 interrupt() 方法来中断线程 T2 的话,你会发现线程 T2 所在 CPU 会飙升到 100%。
- final StampedLock lock
- = new StampedLock();
- Thread T1 = new Thread(()->{
- // 获取写锁
- lock.writeLock();
- // 永远阻塞在此处,不释放写锁
- LockSupport.park();
- });
- T1.start();
- // 保证T1获取写锁
- Thread.sleep(100);
- Thread T2 = new Thread(()->
- //阻塞在悲观读锁
- lock.readLock()
- );
- T2.start();
- // 保证T2阻塞在读锁
- Thread.sleep(100);
- //中断线程T2
- //会导致线程T2所在CPU飙升
- T2.interrupt();
- T2.join();
所以,使用 StampedLock 一定不要调用中断操作,如果需要支持中断功能,一定使用可中断的悲观读锁 readLockInterruptibly() 和写锁 writeLockInterruptibly()。这个规则一定要记清楚
StampedLock 的使用看上去有点复杂,但是如果你能理解乐观锁背后的原理,使用起来还是比较流畅的。建议你认真揣摩 Java 的官方示例,这个示例基本上就是一个最佳实践。
我们把 Java 官方示例精简后,形成下面的代码模板,建议你在实际工作中尽量按照这个模板来使用 StampedLock。
StampedLock 读模板:
- final StampedLock sl = new StampedLock();
- // 乐观读
- long stamp = sl.tryOptimisticRead();
- // 读入方法局部变量
- ......
- // 校验stamp
- if (!sl.validate(stamp)){
- // 升级为悲观读锁
- stamp = sl.readLock();
- try {
- // 读入方法局部变量
- .....
- } finally {
- //释放悲观读锁
- sl.unlockRead(stamp);
- }
- }
- //使用方法局部变量执行业务操作
- ......
StampedLock 写模板:
- long stamp = sl.writeLock();
- try {
- // 写共享变量
- ......
- } finally {
- sl.unlockWrite(stamp);
- }
ReentrantLock 的实现原理





- public void lock() {
- sync.lock();
- }
- final void lock() {
- if (compareAndSetState(0, 1))
- setExclusiveOwnerThread(Thread.currentThread());
- else
- acquire(1);
- }
- CAS 的实现原理
- protected final boolean compareAndSetState(int expect, int update) {
- // See below for intrinsics setup to support this
- return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
- }
- 在 unsafe.cpp 文件中,可以找到 compareAndSwarpInt 的实现
- UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
- UnsafeWrapper("Unsafe_CompareAndSwapInt");
- oop p = JNIHandles::resolve(obj); //将 Java 对象解析成 JVM 的 oop(普通对象指针),
- jint* addr = (jint *) index_oop_from_field_offset_long(p, offset); //根据对象 p和地址偏移量找到地址
- return (jint)(Atomic::cmpxchg(x, addr, e)) == e; //基于 cas 比较并替换, x 表示需要更新的值,addr 表示 state 在内存中的地址,e 表示预期值
- UNSAFE_END
- public final void acquire(int arg) {
- if (!tryAcquire(arg) &&
- acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
- selfInterrupt();
- }
- protected final boolean tryAcquire(int acquires) {
- return nonfairTryAcquire(acquires);
- }
- final boolean nonfairTryAcquire(int acquires) {
- final Thread current = Thread.currentThread();//获取当前执行的线程
- int c = getState();//获得 state 的值
- if (c == 0) {//表示无锁状态
- if (compareAndSetState(0, acquires)) {//cas 替换 state 的值,cas 成功表示获取锁成功
- setExclusiveOwnerThread(current);//保存当前获得锁的线程,下次再来的时候不要再尝试竞争锁
- return true;
- }
- }
- else if (current == getExclusiveOwnerThread()) {//如果同一个线程来获得锁,直接增加重入次数
- int nextc = c + acquires;
- if (nextc < 0) // overflow
- throw new Error("Maximum lock count exceeded");
- setState(nextc);
- return true;
- }
- return false;
}
- private Node addWaiter(Node mode) {
- Node node = new Node(Thread.currentThread(), mode);//把当前线程封装为 Node
- Node pred = tail; //tail 是 AQS 中表示同比队列队尾的属性,默认是 null
- if (pred != null) {//tail 不为空的情况下,说明队列中存在节点
- node.prev = pred;//把当前线程的 Node 的 prev 指向 tail
- if (compareAndSetTail(pred, node)) {//通过 cas 把 node加入到 AQS 队列,也就是设置为 tail
- pred.next = node;//设置成功以后,把原 tail 节点的 next指向当前 node
- return node;
- }
- }
- enq(node);//tail=null,把 node 添加到同步队列
- return node;
- }
- private Node enq(final Node node) {
- for (;;) {
- Node t = tail;
- if (t == null) { // Must initialize
- if (compareAndSetHead(new Node()))
- tail = head;
- } else {
- node.prev = t;
- if (compareAndSetTail(t, node)) {
- t.next = node;
- return t;
- }
- }
- }
}

- final boolean acquireQueued(final Node node, int arg) {
- boolean failed = true;
- try {
- boolean interrupted = false;
- for (;;) {
- final Node p = node.predecessor();//获取当前节点的 prev 节点
- if (p == head && tryAcquire(arg)) {//如果是 head 节点,说明有资格去争抢锁
- setHead(node);//获取锁成功,也就是ThreadA 已经释放了锁,然后设置 head 为 ThreadB 获得执行权限
- p.next = null; //把原 head 节点从链表中移除
- failed = false;
- return interrupted;
- }//ThreadA 可能还没释放锁,使得 ThreadB 在执行 tryAcquire 时会返回 false
- if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())
- interrupted = true; //并且返回当前线程在等待过程中有没有中断过。
- }
- } finally {
- if (failed)
- cancelAcquire(node);
- }
}
- private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
- int ws = pred.waitStatus;//前置节点的waitStatus
- if (ws == Node.SIGNAL)//如果前置节点为 SIGNAL,意味着只需要等待其他前置节点的线程被释放,
- return true;//返回 true,意味着可以直接放心的挂起了
- if (ws > 0) {//ws 大于 0,意味着 prev 节点取消了排队,直接移除这个节点就行
- do {
- node.prev = pred = pred.prev;
- //相当于: pred=pred.prev; node.prev=pred;
- } while (pred.waitStatus > 0); //这里采用循环,从双向列表中移除 CANCELLED 的节点
- pred.next = node;
- } else {//利用 cas 设置 prev 节点的状态为 SIGNAL(-1)
- compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
- }
- return false;
}
- private final boolean parkAndCheckInterrupt() {
- LockSupport.park(this);
- return Thread.interrupted();
- }
- static void selfInterrupt() {
- Thread.currentThread().interrupt();
- }


锁的释放流程
- public final boolean release(int arg) {
- if (tryRelease(arg)) { //释放锁成功
- Node h = head; //得到 aqs 中 head 节点
- if (h != null && h.waitStatus != 0)//如果 head 节点不为空并且状态!=0.调用 unparkSuccessor(h)唤醒后续节点
- unparkSuccessor(h);
- return true;
- }
- return false;
- }
- protected final boolean tryRelease(int releases)
- {
- int c = getState() - releases;
- if (Thread.currentThread() != getExclusiveOwnerThread())
- throw new IllegalMonitorStateException();
- boolean free = false;
- if (c == 0) {
- free = true;
- setExclusiveOwnerThread(null);
- }
- setState(c);
- return free;
- }
- private void unparkSuccessor(Node node) {
- int ws = node.waitStatus;//获得 head 节点的状态
- if (ws < 0)
- compareAndSetWaitStatus(node, ws, 0);// 设置 head 节点状态为 0
- Node s = node.next;//得到 head 节点的下一个节点
- if (s == null || s.waitStatus > 0) {
- //如果下一个节点为 null 或者 status>0 表示 cancelled 状态. //通过从尾部节点开始扫描,找到距离 head 最近的一个waitStatus<=0 的节点
- s = null;
- for (Node t = tail; t != null && t != node; t = t.prev)
- if (t.waitStatus <= 0)
- s = t;
- }
- if (s != null) //next 节点不为空,直接唤醒这个线程即可
- LockSupport.unpark(s.thread);
- }
- private Node enq(final Node node) {
- for (;;) {
- Node t = tail;
- if (t == null) { // Must initialize
- if (compareAndSetHead(new Node()))
- tail = head;
- } else {
- node.prev = t;
- if (compareAndSetTail(t, node)) {
- t.next = node;
- return t;
- }
- }
- }
}

- final boolean acquireQueued(final Node node, int arg) {
- boolean failed = true;
- try {
- boolean interrupted = false;
- for (;;) {
- final Node p = node.predecessor();
- if (p == head && tryAcquire(arg)) {
- setHead(node);
- p.next = null; // help GC
- failed = false;
- return interrupted;
- }
- if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())
- interrupted = true;
- }
- } finally {
- if (failed)
- cancelAcquire(node);
- }
}

公平锁和非公平锁的区别
- final void lock() {
- acquire(1);
- }
- 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;
}
Condition
- public class ConditionDemoWait implements Runnable{
- private Lock lock;
- private Condition condition;
- public ConditionDemoWait(Lock lock, Condition condition){
- this.lock=lock;
- this.condition=condition;
- }
- @Override
- public void run() {
- System.out.println("begin -ConditionDemoWait");
- try {
- lock.lock();
- condition.await();
- System.out.println("end - ConditionDemoWait");
- } catch (InterruptedException e) {
- e.printStackTrace();
- }finally {
- lock.unlock();
- }
- }
}
- public class ConditionDemoSignal implements Runnable{
- private Lock lock;
- private Condition condition;
- public ConditionDemoSignal(Lock lock, Condition condition){
- this.lock=lock;
- this.condition=condition;
- }
- @Override
- public void run() {
- System.out.println("begin -ConditionDemoSignal");
- try {
- lock.lock();
- condition.signal();
- System.out.println("end - ConditionDemoSignal");
- }finally {
- lock.unlock();
- }
- }
}
Condition 源码分析
- public final void await() throws InterruptedException {
- if (Thread.interrupted())
- throw new InterruptedException();
- Node node = addConditionWaiter(); //创建一个新的节点,节点状态为 condition,采用的数据结构仍然是链表
- int savedState = fullyRelease(node); //释放当前的锁,得到锁的状态,并唤醒 AQS 队列中的一个线程
- int interruptMode = 0;
- //如果当前节点没有在同步队列上,即还没有被 signal,则将当前线程阻塞
- while (!isOnSyncQueue(node)) {//判断这个节点是否在 AQS 队列上,第一次判断的是 false,因为前面已经释放锁了
- LockSupport.park(this); // 第一次总是 park 自己,开始阻塞等待
- // 线程判断自己在等待过程中是否被中断了,如果没有中断,则再次循环,会在 isOnSyncQueue 中判断自己是否在队列上.
- // isOnSyncQueue 判断当前 node 状态,如果是 CONDITION 状态,或者不在队列上了,就继续阻塞.
- // isOnSyncQueue 判断当前 node 还在队列上且不是 CONDITION 状态了,就结束循环和阻塞.
- if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
- break;
- }
- // 当这个线程醒来,会尝试拿锁, 当 acquireQueued 返回 false 就是拿到锁了.
- // interruptMode != THROW_IE -> 表示这个线程没有成功将 node 入队,但 signal 执行了 enq 方法让其入队了.
- // 将这个变量设置成 REINTERRUPT.
- if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
- interruptMode = REINTERRUPT;
- // 如果 node 的下一个等待者不是 null, 则进行清理,清理 Condition 队列上的节点.
- // 如果是 null ,就没有什么好清理的了.
- if (node.nextWaiter != null) // clean up if cancelled
- unlinkCancelledWaiters();
- // 如果线程被中断了,需要抛出异常.或者什么都不做
- if (interruptMode != 0)
- reportInterruptAfterWait(interruptMode);
- }
- public final void signal() {
- if (!isHeldExclusively()) //先判断当前线程是否获得了锁
- throw new IllegalMonitorStateException();
- Node first = firstWaiter; // 拿到 Condition 队列上第一个节点
- if (first != null)
- doSignal(first);
- }
- private void doSignal(Node first) {
- do {
- if ( (firstWaiter = first.nextWaiter) == null)// 如果第一个节点的下一个节点是 null, 那么, 最后一个节点也是 null.
- lastWaiter = null; // 将 next 节点设置成 null
- first.nextWaiter = null;
- } while (!transferForSignal(first) &&(first = firstWaiter) != null);
- }
- final boolean transferForSignal(Node node) {
- /*
- * If cannot change waitStatus, the node has been cancelled.
- */
- if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
- return false;
- Node p = enq(node);
- int ws = p.waitStatus;
- // 如果上一个节点的状态被取消了, 或者尝试设置上一个节点的状态为 SIGNAL 失败了(SIGNAL 表示: 他的 next 节点需要停止阻塞),
- if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
- LockSupport.unpark(node.thread); // 唤醒输入节点上的线程.
- return true;
}
多线程(四) AQS底层原理分析的更多相关文章
- AQS底层原理分析
J.U.C 简介 Java.util.concurrent 是在并发编程中比较常用的工具类,里面包含很多用来在并发场景中使用的组件.比如线程池.阻塞队列.计时器.同步器.并发集合等等.并发包的作者是大 ...
- AQS工作原理分析
AQS工作原理分析 一.大致介绍1.前面章节讲解了一下CAS,简单讲就是cmpxchg+lock的原子操作:2.而在谈到并发操作里面,我们不得不谈到AQS,JDK的源码里面好多并发的类都是通过Sy ...
- HashMap底层原理分析(put、get方法)
1.HashMap底层原理分析(put.get方法) HashMap底层是通过数组加链表的结构来实现的.HashMap通过计算key的hashCode来计算hash值,只要hashCode一样,那ha ...
- Activiti工作流学习笔记(三)——自动生成28张数据库表的底层原理分析
原创/朱季谦 我接触工作流引擎Activiti已有两年之久,但一直都只限于熟悉其各类API的使用,对底层的实现,则存在较大的盲区. Activiti这个开源框架在设计上,其实存在不少值得学习和思考的地 ...
- JMM和Volatile底层原理分析
JMM和volatile分析 1.JMM:Java Memory Model,java线程内存模型 JMM:它是一个抽象的概念,描述的是线程和内存间的通信,java线程内存模型和CPU缓存模型类似,它 ...
- springAop:Aop(Xml)配置,Aop注解配置,spring_Aop综合案例,Aop底层原理分析
知识点梳理 课堂讲义 0)回顾Spring体系结构 Spring的两个核心:IoC和AOP 1)AOP简介 1.1)OOP开发思路 OOP规定程序开发以类为模型,一切围绕对象进行,OOP中完成某个任务 ...
- AQS实现原理分析——ReentrantLock
在Java并发包java.util.concurrent中可以看到,不少源码是基于AbstractQueuedSynchronizer(以下简写AQS)这个抽象类,因为它是Java并发包的基础工具类, ...
- Hadoop(十四)MapReduce原理分析
前言 上一篇我们分析了一个MapReduce在执行中的一些细节问题,这一篇分享的是MapReduce并行处理的基本过程和原理. Mapreduce是一个分布式运算程序的编程框架,是用户开发“基于had ...
- 《Exploring in UE4》多线程机制详解[原理分析]
转自:https://zhuanlan.zhihu.com/c_164452593 目录一.概述二."标准"多线程三.AsyncTask系统3.1 FQueuedThreadPoo ...
随机推荐
- kaggle新手如何在平台学习大神的代码
原创:数据臭皮匠 [导读]Kaggle ,作为听说它很牛X但从未接触过的同学,可能仅仅了解这是一个参加数据挖掘比赛的网站,殊不知Kaggle也会有赛题相关的数据集, 比如我们熟知的房价预测.泰坦尼克 ...
- 阅读lodash源码之旅数组方法篇-compact和concat
鲁迅说过:只有阅读过优秀库源码的人,才能配的上是真正的勇士. compact 创建一个新数组,包含原数组中所有的非假值元素.例如false, null,0, "", undefin ...
- 容器化安装Mysql 8.0 并部署主从复制
系统: Centos 7.4 数据库版本:8.0.20 两台机器做相同操作 安装Docker export VERSION=18.06 && curl -fsSL http://rai ...
- 代码 or 指令,浅析ARM架构下的函数的调用过程
摘要:linux程序运行的状态以及如何推导调用栈. 1.背景知识 1.ARM64寄存器介绍: 2.STP指令详解(ARMV8手册): 我们先看一下指令格式(64bit),以及指令对于寄存机执行结果的影 ...
- Django 模型(数据库)-cmd下的操作
Django 模型是与数据库相关的,与数据库相关的代码一般写在 models.py 中,Django 支持 sqlite3, MySQL, PostgreSQL等数据库,只需要在settings.py ...
- ubuntu安装mysql5.6
安装mysql5.6在ubuntu上安装mysql5.6的版本 1.添加mysql5.6的源 sudo apt-get install software-properties-common sudo ...
- LinuxCentos7下安装Mysql8.x以及密码修改
LinuxCentos7下安装Mysql以及密码修改 引言: 之前都是用Docker或者yum自动安装,这次主要是下载压缩包解压安装,中间也有些小波折,记录如下,以供参考: 1.删除旧的MySQL 检 ...
- gin框架之路由前缀树初始化分析
https://mp.weixin.qq.com/s/lLgeKMzT4Q938Ij0r75t8Q
- Linux进程内存用量分析之堆内存篇
https://mp.weixin.qq.com/s/a6mLMDinYQGUSaOsGYCEaA 独家|Linux进程内存用量分析之堆内存篇 姬晨烜 58技术 2019-12-06 导语 本文将介绍 ...
- 一个cgi的例子
cgi的详细资料可以参考 http://httpd.apache.org/docs/2.4/howto/cgi.html 下面是一个python实现的cgi脚本,里面体现了一些cgi的用法,使用其他脚 ...