AbstractQueuedSynchronizer是JUC的核心框架,其设计非常精妙。 使用了 Java 的模板方法模式。 首先试图还原一下其使用场景:

对于排他锁,在同一时刻,N个线程只有1个线程能获取到锁;其他没有获取到锁的线程被挂起放置在队列中,待获取锁的线程释放锁后,再唤醒队列中的线程。

线程的挂起是获取锁失败时调用Unsafe.park()方法;线程的唤醒是由其他线程释放锁时调用Unsafe.unpark()实现。

由于获取锁,执行锁内代码逻辑,释放锁整个流程可能只需要耗费几毫秒,所以很难对锁的争用有一个直观的感受。下面以3个线程来简单模拟一下排他锁的机制。

import sun.misc.Unsafe;

import java.lang.reflect.Field;

import java.util.ArrayList;

import java.util.List;

import java.util.concurrent.locks.LockSupport;

public class AQSDemo {

private static final Unsafe unsafe = getUnsafe();

private static final long stateOffset;

private static Unsafe getUnsafe() {

try {

Field field = Unsafe.class.getDeclaredField("theUnsafe");

field.setAccessible(true);

return (Unsafe)field.get(null);

} catch (Exception e) {

}

return null;

}

static{

try{

stateOffset = unsafe.objectFieldOffset

(AQSDemo.class.getDeclaredField("state"));

} catch (Exception ex) { throw new Error(ex); }

}

private volatile int state;

private List threads = new ArrayList<>();

public void lock(){

if(!unsafe.compareAndSwapInt(state,stateOffset,0,1)){

// 有问题,非线程安全;只作演示使用

threads.add(Thread.currentThread());

LockSupport.park();

Thread.interrupted();

}

}

public void unlock(){

state = 0;

if(!threads.isEmpty()){

Thread first = threads.remove(0);

LockSupport.unpark(first);

}

}

static class MyThread extends Thread{

private AQSDemo lock;

public MyThread(AQSDemo lock){

this.lock = lock;

}

public void run(){

try{

lock.lock();

System.out.println("run ");

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

} finally {

lock.unlock();

}

}

}

public static void main(String[] args) {

AQSDemo lock = new AQSDemo();

MyThread a1 = new MyThread(lock);

MyThread a2 = new MyThread(lock);

MyThread a3 = new MyThread(lock);

a1.start();

a2.start();

a3.start();

}

}

上面的代码,使用park和unpark简单模拟了排他锁的工作原理。使用ArrayList屏蔽了链表多线程环境下链表的构造细节, 该代码实际上在多线程环境中使用是有问题的,发现了么?

通过上面的代码,能理解到多线程环境下,链表为什么能比ArrayList好使。

理解AQS, 其核心在于理解 state

和 head

, tail

三个变量。换句话说,理解AQS, 只需理解 状态

和 链表实现的队列

这两样东西。其使用方式就是,如果更新状态不成功,就把线程挂起,丢到队列中;其他线程使用完毕后,从队列中唤醒一个线程执行。 如果排队的线程数量过多,那么该谁首先获得锁就有讲究,不能暗箱操作,所以有公平和非公平两种策略。

越来越能理解 “编程功底,细节是魔鬼”,理解了上面的使用方式,只相当于理解了需求。那么实现上有那些细节呢? 我们通过问答的方式来阐明。

问题1: state

变量为什么要用volatile关键词修饰?

volatile是synchronized的轻量版本,在特定的场景下具备锁的特点 变量更新的值不依赖于当前值

, 比如 setState()

方法。 当volatile的场景不满足时,使用Unsafe.compareAndSwap即可。

问题2: 链表是如何保证多线程环境下的链式结构?

首先我们看链表是一个双向链表,我们看链表呈现的几个状态:

1. 空链表

(未初始化)

head -- null

tail -- null

or

(初始化后)

head -- Empty Node

tail -- Empty Node

2. 只有一个元素的链表

head -- Empty Node Thread Node -- tail

也就是说,当链表的不为空时, 链表中填充者一个占位节点。

学习数据结构,把插入删除两个操作弄明白,基本就明白这个数据结构了。我们先看插入操作 enq()

:

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;

}

}

}

}

首先一个无限循环。 假如这个链表没有初始化,那么这个链表会通过循环的结构插入2个节点。 由于多线程环境下, compareAndSet会存在失败,所以通过循环保证了失败重试。 为了保证同步,要么依赖锁,要么通过CPU的cas。 这里是实现同步器,只能依赖cas。 这种编程结构,看AtomicInteger,会特别熟悉。

接下来看链表的删除操作。当线程释放锁调用 release()

方法时,AQS会按线程进入队列的顺序唤醒地一个符合条件的线程,这就是FIFO的体现。代码如下:

public final boolean release(int arg) {

if (tryRelease(arg)) {

Node h = head;

if (h != null && h.waitStatus != 0)

unparkSuccessor(h);

return true;

}

return false;

}

这里 unparkSuccessor()

里面的 waitStatus

我们先忽略。这样的话,线程会从阻塞的后面继续执行,从 parkAndCheckInterrupt()

方法中出来。

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);

}

}

由于唤醒的顺序是FIFO, 所以通常 p==head

条件是满足的。如果获取到锁,就把当前节点作为链表的head节点: setHead(node)

, 原head节点从链表中断开,让GC回收 p.next=null

。 也就是说,链表的删除是从头开始删除,以实现FIFO的目标。

到这里,AQS的链表操作就弄清楚了。接下来的疑问就在节点的 waitStatus

里面。

问题: waitStatus的作用是什么?

在AQS, 实现了一个ConditionObject, 就像Object.wait/nofity必须在synchronized中调用一样, JUC实现了一个Object.wait/notify的替代品。这是另一个话题,这里不细说了,后面再研究一下。

最后,总结一下,本文简单分析了一下AQS的实现机制。主要参考ReentrantLock和论文《The java.util.concurrent Synchronizer Framework》。

java aqs实现原理_JAVA基础学习之-AQS的实现原理分析相关推荐

  1. java catch空指针异常处理_Java基础学习:java文件空指针异常处理

    在Java编程中,我们或多或少地会遇到一些程序异常的情况,为了不影响程序的正常执行,我们需要在程序发生异常的情况下,进行一些操作来防止造成更大的损失.例如在我们平时的除法运算中,被除数是不能为0的,如 ...

  2. java同步锁售票_Java基础学习笔记: 多线程,线程池,同步锁(Lock,synchronized )(Thread类,ExecutorService ,Future类)(卖火车票案例)...

    学习多线程之前,我们先要了解几个关于多线程有关的概念. 进程:进程指正在运行的程序.确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能. 线程:线程是 ...

  3. java键盘输入运算符_Java基础学习-三元运算符和键盘录入的基本步骤和使用

    1.键盘录入数据概述 我们目前在写程序的时候,数据值都是固定的,但是实际开发中,数据值肯定是变化的,所以,把数据改进为键盘录入,提高程序的灵活性. 2.如何实现键盘录入数据呢?(目前先记住就行) 导包 ...

  4. java数据类型转换方向_java基础学习篇02(数据类型转换和基本面试题)

    JAVA基本数据类型转换 java中的数据类型,分为,引用数据类型和基本数据类型.基本数据类型有8中 整型:byte 8位 . short 16位 .int 32位 .long 64位. (8位=1个 ...

  5. java入门第六天课程_Java基础学习第六天 小游戏

    /** * 游戏开始 * @author LENOVO * */ public class Game_Start { public static void main(String[] args) { ...

  6. java 注解 对象_Java基础-学习使用Annotation注解对象

    Java基础-学习使用Annotation注解对象 注解(也被称为元数据)为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某 个时刻非常方便地使用这些数据 1-1:基本语法 Java S ...

  7. java三板斧_JAVA基础学习 三板斧

    JAVA基础学习 -1 三板斧 1. 引子 最近因为工作需要,开始学习JAVA,我之前常用的编程语言是C,C#,C++,基本没有用JAVA,JAVA需要从基础开始学习了. JAVA是从C++优化而来: ...

  8. 黑马程序员_毕向东_Java基础视频教程_Java基础学习知识点总结

    黑马程序员_毕向东_Java基础视频教程 Java基础学习知识点总结 2016年01月06日  day01 一.基础知识:软件开发 1.什么是软件?软件:一系列按照特定顺序组织的计算机数据和指令的集合 ...

  9. 用java制作心理测试软件_0基础学习制作app

    学会app制作通常需要多长时间,零基础 如果从目的上来说的话!我有2个建议; 1.用追信魔盒APP制作平台,这个平台可以设置RSS网络链接,对新闻的各个分类也可以做排版. 2.用应用公园APP开发制作 ...

最新文章

  1. js array 删除指定元素_数组--学习笔记(数据结构数组 /js数组)
  2. android处理url中的特殊字符
  3. python爬虫怎么做毕业设计_python语言爬虫做成毕业设计的话,怎样答辩演示,或者怎样把爬虫复杂化?...
  4. html 子框架刷新,webpack 热更新 只对改变 CSS 有效 改变 HTML 页面会刷新 没用其他框架。...
  5. 解析ASP网页的执行顺序
  6. vsftpd配置文件丢失
  7. java for循环 嵌套for循环-标签使用
  8. tomat(16)关闭钩子
  9. 我的世界JAVA刷怪范围_《我的世界》只有刷怪蛋能够刷新生物吗?并不是,还有一种物品!...
  10. lamp mysql5.0_CentOS 5/6 LAMP(Apache MySQL PHP)一键安装脚本
  11. android 跳转动画黑屏,Android8.0 适配解决页面跳转过程出现短暂黑屏的问题
  12. 怎么读取can报文_【案例】东风天龙“仪表未收到EECU报文”
  13. 华为Mate50渲染图曝光:经典奥利奥相机模组
  14. DNS配置,主从,子域,转发
  15. ModelAndView简介
  16. 【系列三之CentOS系列】Shell编程入门(3)
  17. 【原创】MySQL Replay线上流量压测工具
  18. 关机时Ubuntu-Unattended upgrade in progress during shutdown
  19. vue-meta实现router动态设置meta标签
  20. windows10桌面图标异常,显示为白色图标

热门文章

  1. python 相关性检验怎么计算p值_生信工具 | 相关性热图还能玩出什么花样?
  2. html加载swf 进度条,教你用FLASH如何制作完整的loading
  3. 朋友圈9宫格留白_九宫格招聘拼图图片
  4. python判断网页密码加密方式_Python模拟网页中javascript加密与验证的相关处理
  5. python return用法_遗传算法(Python) #4 DEAP框架入门
  6. linux下备份mysql日志_Linux下使用Logrotate对MySQL日志进行轮转备份
  7. linux拷贝到新建文件夹命令行,Linux创建文件touch,复制文件cp,tab补全,链接文件ln命令...
  8. arm汇编指令WFI和WFE
  9. hdfs复制文件夹_Hadoop框架:HDFS简介与Shell管理命令
  10. window环境读linux文件,Windows本地环境和Linux腾讯云服务器之间传输文件的方法