上一篇简单介绍了AQS,我们大概知道AQS就是一个框架,把很多功能都给实现了(比如入队规则,唤醒节点中的线程等),我们如果要使用的话只需要实现其中的一些方法(比如tryAcquire等)就行了!这次主要说说AQS中阻塞队列的的入队规则还有条件变量;

一.AQS入队规则

  我们仔细分析一下AQS是如何维护阻塞队列的,在独占方式获取资源的时候,是怎么将竞争锁失败的线程丢到阻塞队列中的呢?

  我们看看acquire方法,这里首先会调用子类实现的tryAcquire方法尝试修改state,修改失败的话,说明线程竞争锁失败,于是会走到后面的这个条件;

  这个addWaiter方法就是将当前线程封装成一个Node.EXCLUSIVE类型的节点,然后丢到阻塞队列中;

  第一次还没有阻塞队列的时候,会到enq方法里面,我们仔细看看enq方法

  enq()方法中,我们在第一次进入这个方法的时候,下面图一所示,tail和head都指向null;

  第一次循环,到首先会到图二,然后判断t所指向的节点是不是null,如果是的话,就用CAS更新节点,这个CAS我们可以看作:头节点head为null,我们把head节点更新为一个哨兵节点(哨兵节点就是new Node()),再将tail也指向head,就是图三了

  第二次for循环:走到上面的else语句,将新节点的前一个节点设置为哨兵节点;

  然后就是CAS更新节点,这里CAS的意思:如果最后的节点tail指向的和t是一样的,那么就将tail指向node节点

  最后再将t的下一个节点设置为node,下图所示,就ok了

二.AQS条件变量的使用

  什么是条件变量呢?我们在开始介绍AQS的时候,还有一个内部类没有说,就是ConditionObject,还记得前面说过的Unsafe中的park和unpark方法吗?而这个ConditionObject就对这两个方法进行了一次封装,await()和signal()方法,但是更灵活,可以创建多个条件变量,每个条件变量维护一个条件队列(就是一个单向链表,可以看到Node这个内部类中个属性是nextWaiter);

  注意:每一个条件变量里面都维护了一个条件队列

  举个例子,如下所示;

package com.example.demo.study;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock; public class Study0201 { public static void main(String[] args) throws InterruptedException {
// 创建锁对象
ReentrantLock lock = new ReentrantLock();
// 创建条件变量
Condition condition = lock.newCondition();
// 以下创建两个线程,里面都会获取锁和释放锁
Thread thread1 = new Thread(() -> {
lock.lock();
try {
System.out.println("await begin");
// 注意,这里调用条件变量的await方法,当前线程就会丢到condition条件变量中的条件队列中阻塞
condition.await();
System.out.println("await end");
} catch (InterruptedException e) {
//
} finally {
lock.unlock();
} }); Thread thread2 = new Thread(() -> {
lock.lock();
try {
System.out.println("signal begin");
// 唤醒被condition变量内部队列中的某个线程
condition.signal();
System.out.println("signal end");
} finally {
lock.unlock();
}
});
thread1.start();
Thread.sleep(500);
thread2.start();
}
}

  还可以创建多个条件变量,如下所示,每一个条件变量都维护了一个条件队列:

package com.example.demo.study;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock; public class Study0201 { public static void main(String[] args) throws InterruptedException {
// 创建锁对象
ReentrantLock lock = new ReentrantLock();
// 创建条件变量1
Condition condition1 = lock.newCondition();
//条件变量2
Condition condition2 = lock.newCondition(); // 以下创建两个线程,里面都会获取锁和释放锁
Thread thread1 = new Thread(() -> {
lock.lock();
try {
System.out.println("await begin");//1
condition1.await();
System.out.println("await end");//5 System.out.println("condition2---signal---start");//6
condition2.signal();
System.out.println("condition2---signal---endend");//7
} catch (InterruptedException e) {
//
} finally {
lock.unlock();
} }); Thread thread2 = new Thread(() -> { lock.lock();
try {
System.out.println("signal begin");//2
condition1.signal();
System.out.println("signal end");//3 System.out.println("condition2---await---start");//4
condition2.await();
System.out.println("condition2---await---end");//8
} catch (InterruptedException e) {
//
} finally {
lock.unlock();
} }); thread1.start();
Thread.sleep(500);
thread2.start(); } }

三.走进条件变量

  我们看看上面的获取条件变量的方式Condition condition1 = lock.newCondition(),我们打开newCondition方法,最后就是创建一个ConditionObject实例;这个类是AQS的内部类,通过这个类可以访问AQS内部的属性和方法;

  注意:在调用await方法和signal方法之前,必须要先获取锁

  然后我们再看看条件变量的await方法,下图所示,我们可以进入到addConditionWaiter()方法内部看看:

public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//新建一个Node.CONDITION节点放到条件队列最后面
Node node = addConditionWaiter();
//释放当前线程获取的锁
int savedState = fullyRelease(node);
int interruptMode = 0;
//调用park()方法阻塞挂起当前线程
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
private Node addConditionWaiter() {
Node t = lastWaiter;
//第一次进来,这个lastWaiter是null,即t = null,不会进入到这个if语句
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
//创建一个Node.CONDITION类型的节点,然后下面这个if中就是将第一个节点firstWaiter和最后一个节点都指向这个新创建的节点
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}

  顺便在看看signal方法:

public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//条件队列移除第一个节点,然后把这个节点丢到阻塞队列中,然后激活这个线程
Node first = firstWaiter;
if (first != null)
doSignal(first);
}

  我们想一想在AQS中阻塞队列和条件队列有什么关系啊?

  1.当多个线程调用lock.lock()方法的时候,只有一个线程获取到可锁,其他的线程都会被转为Node节点丢到AQS的阻塞队列中,并做CAS自旋获取锁;

  2.当获取到锁的线程对应的条件变量的await()方法被调用的时候,该线程就会释放锁,并把当前线程转为Node节点放到条件变量对应的条件队列中;

  3.这个时候AQS的阻塞队列中又会有一个节点中的线程能得到锁了,如果这个线程又恰巧调用了对应条件变量的await()方法时,又会重复2的步骤,然后阻塞队列中又会有一个节点中的线程获得锁

  4.然后,又有一个线程调用了条件变量的signal()或者signalAll()方法,就会把条件队列中一个或者所有的节点都移动到AQS阻塞队列中,然后调用unpark方法进行授权,就等着获得锁了;

  一个锁对应一个阻塞队列,但是对应多个条件变量,每一个条件变量对应一个条件队列;其中,这两种队列中存放的都是Node节点,Node节点中封装了线程及其状态,下图所示:

看看AQS阻塞队列和条件队列的更多相关文章

  1. AQS源码深入分析之条件队列-你知道Java中的阻塞队列是如何实现的吗?

    本文基于JDK-8u261源码分析 1 简介 因为CLH队列中的线程,什么线程获取到锁,什么线程进入队列排队,什么线程释放锁,这些都是不受我们控制的.所以条件队列的出现为我们提供了主动式地.只有满足指 ...

  2. 深入浅出AQS之条件队列

    相比于独占锁跟共享锁,AbstractQueuedSynchronizer中的条件队列可能被关注的并不是很多,但它在阻塞队列的实现里起着至关重要的作用,同时如果想全面了解AQS,条件队列也是必须要学习 ...

  3. 浅谈Java中的Condition条件队列,手摸手带你实现一个阻塞队列!

    条件队列是什么?可能很多人和我一样答不出来,不过今天终于搞清楚了! 什么是条件队列 条件队列:当某个线程调用了wait方法,或者通过Condition对象调用了await相关方法,线程就会进入阻塞状态 ...

  4. Java并发系列[4]----AbstractQueuedSynchronizer源码分析之条件队列

    通过前面三篇的分析,我们深入了解了AbstractQueuedSynchronizer的内部结构和一些设计理念,知道了AbstractQueuedSynchronizer内部维护了一个同步状态和两个排 ...

  5. 并发条件队列之Condition 精讲

    1. 条件队列的意义 Condition将Object监控器方法( wait , notify和notifyAll )分解为不同的对象,从而通过与任意Lock实现结合使用,从而使每个对象具有多个等待集 ...

  6. 《java并发编程实战》读书笔记11--构建自定义的同步工具,条件队列,Condition,AQS

    第14章 构建自定义的同步工具 本章将介绍实现状态依赖性的各种选择,以及在使用平台提供的状态依赖机制时需要遵守的各项规则. 14.1 状态依赖性的管理 对于并发对象上依赖状态的方法,虽然有时候在前提条 ...

  7. Java高级:条件队列与同步器Synchronizer的原理+AQS的应用

    14.构建自定义的同步工具 类库中包含了许多存在状态依赖性的类,例如FutureTask,Semaphore和BlockingQueue等.在这些类中的一些操作中有着基于状态的前提条件,例如,不能从一 ...

  8. Java并发框架——AQS阻塞队列管理(二)——自旋锁优化

    看Craig, Landin, and Hagersten发明的CLH锁如何优化同步带来的花销,其核心思想是:通过一定手段将所有线程对某一共享变量轮询竞争转化为一个线程队列且队列中的线程各自轮询自己的 ...

  9. 【Java并发编程实战】----- AQS(四):CLH同步队列

    在[Java并发编程实战]-–"J.U.C":CLH队列锁提过,AQS里面的CLH队列是CLH同步锁的一种变形.其主要从两方面进行了改造:节点的结构与节点等待机制.在结构上引入了头 ...

随机推荐

  1. Java 注解与单元测试

    注解 Java注解是在JDK1.5 之后出现的新特性,用来说明程序的,注解的主要作用体现在以下几个方面: 编译检查,例如 @Override 编写文档,java doc 会根据注解生成对应的文档 代码 ...

  2. Cmder安装与使用

    越来越多人使用Cmder代替Windows的cmd(毕竟其界面太Lower了),但是每次用Cmder都要回到安装目录查找之后才能使用,真的很麻烦,有木有可以像Git一样右键就是可以用的方法呢?答案当然 ...

  3. Struts2 注释类型

    Struts 2 应用程序可以使用Java5注释作为替代XML和Java属性配置.这里是清单的不同的类别有关的最重要的注解: 命名空间注释(动作注释): @ Namespace注释允许在Action类 ...

  4. 在linux下find和grep

    在linux下面工作,有些命令能够大大提高效率.本文就向大家介绍find.grep命令,他哥俩可以算是必会的linux命令,我几乎每天都要用到他们.本文结构如下: find命令 find命令的一般形式 ...

  5. saltstack的配置配置

    一.为不同的环境设置不同的文件目录 1.1 修改配置文件 /etc/salt/master [root@node1 salt]# vim /etc/salt/master file_roots: ba ...

  6. spring boot(三)Junit 测试controller

    Junit测试Controller(MockMVC使用),传输@RequestBody数据解决办法 一.单元测试的目的 简单来说就是在我们增加或者改动一些代码以后对所有逻辑的一个检测,尤其是在我们后期 ...

  7. ansible安装与核心组件详解

    第1章 安装anisble 1.1 安装epel源 rpm -Uvh https://dl.fedoraproject.org/pub/epel/epel-release-latest-6.noarc ...

  8. 3.24 7.13 Python基础汇总

    对象类型 类型名称 示例 简要说明 备注 数字 int,float,complex 1234,3.14,1.3e5,3+4j 数字大小没有限制 十六进制用0x前缀和0-9,a-f表示 字符串 str ...

  9. j接近50道经典SQL练习题,附建表SQL解题SQL

    说明 本文章整理了47道常见sql联系题,包括建表语句,表结构,习题列表,解题答案都涵盖在本文章内.文末提供了所用SQL脚本下载链接.所有解题答案都是本人自己写的,广大读者如果在阅读使用中,有任何问题 ...

  10. 1094 谷歌的招聘 (20 分)C语言

    2004 年 7 月,谷歌在硅谷的 101 号公路边竖立了一块巨大的广告牌(如下图)用于招聘.内容超级简单,就是一个以 .com 结尾的网址,而前面的网址是一个 10 位素数,这个素数是自然常数 e ...