java.util.concurrent中提供了许多同步器,比如常用的ReentranLock,ReentrantReadWriteLock ,Semaphore和CountDownLatch。他们都依赖于AbstractQueuedSynchronizer类提供的处理。

ReentranLock

ReentranLock是重入锁,和Synchronized类似。最大不同是synchronized是基于JVM层面实现的,而Lock是基于JDK层面实现的。

  1. 使用方法:
ReentrantLock lock = new ReentrantLock();
lock.lock();
lock.unlock();
  1. 涉及到的类:
  2. 公平锁和非公平锁
     ReentranLock分为公平锁和非公平锁,区别就在得到锁的机会是否和排队顺序相关。
     如果锁被另一个线程持有,那么申请锁的其他线程会被挂起等待,加入等待队列。先调用lock函数被挂起等待的线程会排在等待队列的前端,后调用的就排在后边。如果此时,锁被释放,需要通知等待线程再次尝试获取锁。
     公平锁会让最先进入队列的线程获得锁。而非公平锁则会在lock时尝试获取锁,所以可能会导致后来的线程先获得了锁,这就是非公平。
    public ReentrantLock() {sync = new NonfairSync();}public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();}

AbstractQueuedSynchronizer

  AQS 是AbstractQueuedSynchronizer的简称。它提供了一个基于FIFO队列,可以用于构建锁或其他相关同步的基础框架。

  该同步器利用了一个int来表示状态,它提供能够实现大部分同步需求的基础。在多线程环境中对状态的操纵必须确保原子性,因此对于状态的处理,使用同步器提供的以下三个方法对状态进行操作:

java.util.concurrent.locks.AbstractQueuedSynchronizer.getState()
java.util.concurrent.locks.AbstractQueuedSynchronizer.setState(int)
java.util.concurrent.locks.AbstractQueuedSynchronizer.compareAndSetState(int, int)
  1. 加锁
    ReentranLock的lock,直接调用了sync的lock方法(FairSync或NonfairSync的lock)。
    // ReentranLockpublic void lock() {sync.lock();}// FairSyncfinal void lock() {acquire(1);}// NonfairSyncfinal void lock() {if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());elseacquire(1);}

  AQS的acquire方法中,tryAcquire先尝试获取锁,如果得到锁,就正常执行。如果得不到锁,先执行addWaiter给当前线程创建一个节点,并将其加入等待队列,然后acquireQueued尝试获取锁。依然获取不到锁,调用selfInterrupt进行终端处理。

public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();
}
  1. 尝试获取锁
    tryAcquire在具体的同步器中实现,以FairSync的实现为例,代码如下:
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;
}

  getState取得AQS中的state变量,值为0表示还未被线程占有。hasQueuedPredecessors判断当前阻塞队列上有没有先来的线程在等待(UnfairSync没有这个判断)。CAS乐观锁尝试改变独占性变量,如果成功,那么表示当前线程获得该变量的所有权,也就是获得锁成功。setExclusiveOwnerThread将本线程设置为独占性变量所有者线程。
  如果该线程已经获取了独占性变量的所有权,那么根据重入性原理,将state值进行加1,表示多次lock。
  其他情况都说明获取锁失败。

compareAndSetState函数,使用CAS操作来设置state的值,而且state值设置了volatile修饰符,确保修改state的值不会出现多线程问题。

  1. 加入队列
    addWaiter方法中调用compareAndSetTail尝试将当前线程加入到队列,如果失败在调用enq(更加复杂耗时的算法)加入队列。enq使用for循环,不停的尝试加入队列。
private Node addWaiter(Node mode) {Node node = new Node(Thread.currentThread(), mode);Node pred = tail;if (pred != null) {node.prev = pred; if (compareAndSetTail(pred, node)){ pred.next = node;return node;}}enq(node);return node;
}
private Node enq(final Node node) {for (;;) { Node t = tail;if (t == null) { //初始化if (compareAndSetHead(new Node())) tail = head;} else {node.prev = t;if (compareAndSetTail(t, node)) {t.next = node;return t;}}}
}
  1. 阻塞队列
    acquireQueued会执行一个for循环,直到获取锁,才返回。
    循环中:判断当前节点是否应该获得这个变量(在队首了)?如果是队首,tryAcquire再次尝试获取独占性变量。如果成功,返回false,正常处理(即不用进入阻塞)。如果获取失败,就调用shouldParkAfterFailedAcquire判断是否应该进入阻塞状态。如果当前节点之前的节点已经进入阻塞状态了,那么就可以判定当前节点不可能获取到锁,为了防止CPU不停的执行for循环,消耗CPU资源,调用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); //如果成工,那么就将自己设置为headp.next = null; // help GCfailed = false;return interrupted;}if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())//调用parkAndCheckInterrupt进行阻塞,然后返回是否为中断状态interrupted = true;}} finally {if (failed)cancelAcquire(node);}
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {int ws = pred.waitStatus;if (ws == Node.SIGNAL) //前一个节点在等待通知,所以当前节点可以阻塞return true;if (ws > 0) { //如果前一节点是取消状态,则跳过前一节点do {node.prev = pred = pred.prev;} while (pred.waitStatus > 0);pred.next = node;} else {//将前一节点的状态设置为signal,返回false,compareAndSetWaitStatus(pred, ws, Node.SIGNAL);}return false;
}
private final boolean parkAndCheckInterrupt() {LockSupport.park(this); //将AQS对象自己传入return Thread.interrupted();
}
  1. 阻塞和中断
    AQS通过调用LockSupport的park方法来执行阻塞当前进程的操作。
public static void park(Object blocker) {Thread t = Thread.currentThread();setBlocker(t, blocker);//设置阻塞对象,用来记录线程被谁阻塞的,用于线程监控和分析工具来定位UNSAFE.park(false, 0L);setBlocker(t, null);
}
  1. 释放锁
    unlock操作调用了AQS的relase方法来释放独占性变量。如果成功,那么就看是否有等待锁的阻塞线程,如果有,就调用unparkSuccessor来唤醒他们。
    可重入锁的体现,只有等到state的值为0时,才代表锁真正被释放了。所以独占性变量state的值就代表锁的有无。当state=0时,表示锁未被占有,否在表示当前锁已经被占有。
    调用了unpark方法后,被阻塞的线程就恢复到运行状态,就会再次执行acquireQueued中的无限for循环中的操作,再次尝试获取锁。
public final boolean release(int arg) {if (tryRelease(arg)) { Node h = head;if (h != null && h.waitStatus != 0)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) {.....//一般情况下,需要唤醒的线程就是head的下一个节点,但是如果它获取锁的操作被取消,或在节点为null时//就直接继续往后遍历,找到第一个未取消的后继节点.Node s = node.next;if (s == null || s.waitStatus > 0) {s = null;for (Node t = tail; t != null && t != node; t = t.prev)if (t.waitStatus <= 0)s = t;}if (s != null)LockSupport.unpark(s.thread);
}

整体概念图

理解Java - AQS相关推荐

  1. 扔掉源码,15张图带你彻底理解java AQS

    java中AQS是AbstractQueuedSynchronizer类,AQS依赖FIFO队列来提供一个框架,这个框架用于实现锁以及锁相关的同步器,比如信号量.事件等. 在AQS中,主要有两部分功能 ...

  2. 深入理解Java并发框架AQS系列(四):共享锁(Shared Lock)

    深入理解Java并发框架AQS系列(一):线程 深入理解Java并发框架AQS系列(二):AQS框架简介及锁概念 深入理解Java并发框架AQS系列(三):独占锁(Exclusive Lock) 深入 ...

  3. 精美图文讲解Java AQS 共享式获取同步状态以及Semaphore的应用

    前言 上一篇万字长文 Java AQS队列同步器以及ReentrantLock的应用 为我们读 JUC 源码以及其设计思想做了足够多的铺垫,接下来的内容我将重点说明差异化,如果有些童鞋不是能很好的理解 ...

  4. 深入理解 Java 内存模型(转载)

    摘要: 原创出处 http://www.54tianzhisheng.cn/2018/02/28/Java-Memory-Model/ 「zhisheng」欢迎转载,保留摘要,谢谢! 0. 前提 &l ...

  5. 深入理解 Java内存模型

    深入理解 Java内存模型 原文地址:http://www.54tianzhisheng.cn/2018/02/28/Java-Memory-Model/ 本文主要内容有 Java 内存模型的基础.重 ...

  6. 深入理解 Java 内存模型 JMM

    前提 <深入理解 Java 内存模型>程晓明著,该书在以前看过一遍,现在学的东西越多,感觉那块越重要,于是又再细看一遍,于是便有了下面的读书笔记总结.全书页数虽不多,内容讲得挺深的.细看的 ...

  7. 深入理解 Java 锁与线程阻塞

    相信大家对线程锁和线程阻塞都很了解,无非就是 synchronized, wait/notify 等, 但是你有仔细想过 Java 虚拟机是如何实现锁和阻塞的呢?它们之间又有哪些联系呢?如果感兴趣的话 ...

  8. 一文带你理解Java中Lock的实现原理

    转载自   一文带你理解Java中Lock的实现原理 当多个线程需要访问某个公共资源的时候,我们知道需要通过加锁来保证资源的访问不会出问题.java提供了两种方式来加锁,一种是关键字:synchron ...

  9. Java AQS模型

    在Java 1.5之前,共享变量的互斥访问一般是通过synchronized代码块来实现,synchronized在Java早期只有重量级锁模式,在并发不是很高的环境下性能比较低,并且synchron ...

  10. Java 并发编程解析 | 如何正确理解Java领域中的锁机制,我们一般需要掌握哪些理论知识?

    苍穹之边,浩瀚之挚,眰恦之美: 悟心悟性,善始善终,惟善惟道! -- 朝槿<朝槿兮年说> 写在开头 提起Java领域中的锁,是否有种"道不尽红尘奢恋,诉不完人间恩怨"的 ...

最新文章

  1. Objective C内存管理之理解autorelease------面试题
  2. 会员系统整合的想法[择]
  3. 当前元素_90行代码,15个元素实现无限滚动
  4. 【Android游戏开发十八】解放手指,利用传感器开发游戏!
  5. CentOS下安装semanage
  6. 保送清华成博士,华为 12 年搞通信,他为何如此看待 5G ?| 人物志
  7. pycharm 中文_环境搭建:3.pycharm社区版安装配置
  8. 第一次申请去美国面签,需要注意哪些事项提高成功率?
  9. 常用矢量图有哪些格式?AI文件存储为psd分层
  10. 页面适配之pt、px、em、rem用法和特点
  11. DWM 层 -- 访客 UV 计算
  12. 荣耀magicbook15C语言,荣耀 MagicBook 15评测 聊聊目前性价比最高的大屏轻薄本
  13. python xlsm_Excel中的xls、xlsx、xlsm混合文件,看我如何用Python统一处理!
  14. jqwidgets简单技术
  15. Glide加载图片会出现淡绿色的背景
  16. Can not connect to the Service chromedriver的解决方法
  17. 微信小程序 关于下载文件、打开文件预览文件(wx.downloadFile和wx.openDocument)
  18. js实现上下左右移动小方块
  19. 【 C++ 】AVL树
  20. 使用matlab绘制条形图、面积图、饼图、柱状图

热门文章

  1. 洛谷 2051 [AHOI2009] 中国象棋
  2. Swarm Mode服务管理
  3. 循环移动(cyclic)
  4. tts语音合成小玩具
  5. 单例设计模式(懒汉式,饿汉式)
  6. Web Audio介绍
  7. weblogic + apache 负载均衡与Session复制
  8. tomcat老启动不起来问题
  9. 11.docker tag
  10. 59. web 攻击技术(3)