Java多线程:线程间通信之Lock中我们提到了ReentrantLock是API级别的实现,但是没有说明其具体实现原理。实际上,ReentrantLock的底层实现使用了AQS(AbstractQueueSynchronizer)。AQS本身仅仅是一个框架,定义了一套多线程访问共享资源的同步框架,可以实现ReentrantLock, Semaphore, CountDownLatch等多线程类。

AQS框架维护了一个资源state(volatile int)和一个同步队列。其中对state的访问包括三种方法:getState(), setState(), compareAndSetState()。其中,compareAndSetState()是原子操作,底层是CAS实现。

AQS框架包含两种可供选择的实现方式:独占(Exclusive)和共享(Share)。由于不同自定义同步器征用共享资源的方式不同,自定义同步器实现时只需实现共享资源state的获取与释放方式即可,而不需要考虑队列的维护。下面简述AQS框架中独占锁和共享锁的获取,释放流程。

独占锁流程

获取时首先调用acquire(acquires),之后进入tryAcquire(acquires)尝试获取锁,若成功则返回。若失败则将当前线程构造为Node节点,CAS插入到同步队列尾部,该线程自旋。自旋时判断其前驱节点是否为头节点,是否成功获取同步状态,二者皆成立则当前节点设置为头节点,否则挂起当前线程等待被前驱节点唤醒。

释放时首先调用release(acquires),之后进入tryRelease(acquires)释放同步状态,之后获取同步队列中当前节点的下一节点并唤醒。

共享锁流程

获取时首先调用acquireShared(acquires),之后进入tryAcquireShared(acquires)获取同步状态,返回值不小于0则说明同步状态有剩余,获取成功直接返回。若返回值小于0则说明获取同步状态失败,构造Node节点CAS插入同步队列尾部并自旋检查前驱节点是否为头节点且成功获取同步状态,若是则当前节点设为头节点,否则挂起等待被前驱节点唤醒。

释放时调用releaseShared(acquires)释放同步状态,之后遍历整个队列唤醒所有后继节点。

独占锁和共享锁实现区别

  • 独占锁的state值为1,同一时刻只有一个线程成功获取同步状态。共享锁state>1,取值由自定义同步器决定。
  • 独占锁队列头节点运行完毕释放锁后唤醒直接后继节点,共享锁唤醒所有后继节点。
  • 共享锁会出现多个线程同时成功获取同步状态的情况。

重入锁的实现

Java中的ReentrantLock和synchronized都是可重入锁,synchronized由JVM实现,重入锁实现时最主要的逻辑是判断上次获取锁的线程是否为当前线程,ReentrantLock基于AQS实现,提供公平锁和非公平锁两种方式,非公平锁实现逻辑如下:

final boolean nonfairTryAcquire(int acquires) {
//获取当前线程
final Thread current = Thread.currentThread();
//通过AQS获取同步状态
int c = getState();
//同步状态为0,说明临界区处于无锁状态,
if (c == 0) {
//修改同步状态,即加锁
if (compareAndSetState(0, acquires)) {
//将当前线程设置为锁的owner
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;
}

公平锁的实现逻辑如下,与非公平锁的区别为判断当前节点是否存在前驱节点,只有等待前驱节点释放后才能获取锁。

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

读写锁的实现

Java的ReentrantReadWriteLock是读写锁实现,其原理是将state变量的高16位和低16位拆分,高16位表示读锁,低16位表示写锁。其写锁tryAcquire(acquires)实现如下:

  • 获取同步状态,分离出低16位的写锁状态。
  • 同步状态不为0,则存在读锁或写锁。
  • 若存在读锁,则不能获取写锁。
  • 若当前线程不是上次获取写锁的线程,则不能获取写锁。
  • 以上判断通过,对低16位(写锁同步状态)进行CAS修改。
  • 当前线程设为写锁的获取线程。

其读锁的tryAcquire(acquires)实现如下:

  • 获取当前同步状态,计算高16位为读锁状态+1后的值。
  • 若大于能获取到的读锁的最大值,则抛出异常。
  • 若存在写锁且当前线程不是写锁获取者,则获取读锁失败。
  • 若上述判断都通过,则利用CAS重新设置读锁的同步状态。

写写锁释放与普通独占锁基本相同,在写锁释放中不断减少读锁的同步状态,同步状态为0时才能完全释放;读锁释放过程中不断释放写锁状态,直到为0,表示没有线程获取读锁。

参考文献

Java技术之AQS详解

Java并发-AQS及各种Lock锁的原理

Java多线程:AQS的更多相关文章

  1. Java多线程——AQS框架源码阅读

    AQS,全称AbstractQueuedSynchronizer,是Concurrent包锁的核心,没有AQS就没有Java的Concurrent包.它到底是个什么,我们来看看源码的第一段注解是怎么说 ...

  2. Java多线程--AQS

    ReentrantLock和AQS的关系 首先我们来看看,如果用java并发包下的ReentrantLock来加锁和释放锁,是个什么样的: 1 ReentrantLock reentrantLock ...

  3. Java多线程系列--AQS之 LockSupport

    concurrent包是基于AQS (AbstractQueuedSynchronizer)框架的,AQS(JAVA CAS原理.unsafe.AQS)框架借助于两个类: Unsafe(提供CAS操作 ...

  4. Java多线程并发06——CAS与AQS

    在进行更近一步的了解Java锁的知识之前,我们需要先了解与锁有关的两个概念 CAS 与 AQS.关注我的公众号「Java面典」了解更多 Java 相关知识点. CAS(Compare And Swap ...

  5. Java多线程专题4: 锁的实现基础 AQS

    合集目录 Java多线程专题4: 锁的实现基础 AQS 对 AQS(AbstractQueuedSynchronizer)的理解 Provides a framework for implementi ...

  6. 40个Java多线程问题总结

    前言 Java多线程分类中写了21篇多线程的文章,21篇文章的内容很多,个人认为,学习,内容越多.越杂的知识,越需要进行深刻的总结,这样才能记忆深刻,将知识变成自己的.这篇文章主要是对多线程的问题进行 ...

  7. Java多线程系列--“JUC锁”03之 公平锁(一)

    概要 本章对“公平锁”的获取锁机制进行介绍(本文的公平锁指的是互斥锁的公平锁),内容包括:基本概念ReentrantLock数据结构参考代码获取公平锁(基于JDK1.7.0_40)一. tryAcqu ...

  8. Java多线程系列--“JUC锁”04之 公平锁(二)

    概要 前面一章,我们学习了“公平锁”获取锁的详细流程:这里,我们再来看看“公平锁”释放锁的过程.内容包括:参考代码释放公平锁(基于JDK1.7.0_40) “公平锁”的获取过程请参考“Java多线程系 ...

  9. Java多线程系列--“JUC锁”01之 框架

    本章,我们介绍锁的架构:后面的章节将会对它们逐个进行分析介绍.目录如下:01. Java多线程系列--“JUC锁”01之 框架02. Java多线程系列--“JUC锁”02之 互斥锁Reentrant ...

随机推荐

  1. FreeSWITCH Git版本管理

    由于测试FreeSWITCH不同版本的需要,研究了下Git的使用,通过Git来管理所有的版本,方便了测试.以下就总结下具体的使用方法: 其中:git clone ..是现在git仓库:git tag ...

  2. Flutter学习笔记与整合

    1.Dart 面向对象语言,与java类比学习 非常适合移动和Web应用程序 1.dart官网 2.Dark2 中文文档 3.Dart语法学习 4.极客学院Dart学习 5.Flutter与Dart ...

  3. robotium之webview元素处理

    今天写robotium脚本发现,用uiautomatorviewer定位百度贴吧的登录框是无法定位的,如图: 明显无法定位用户名.密码输入框,无法定位元素那就无法对控件无法操作 如何定位webview ...

  4. labelImg:no module named pyqt4

    最新版的labelImg安装会出错,改变环境变量,在python3.5中就可以了 参考 shaform :https://github.com/tzutalin/labelImg/issues/106

  5. 对 Laravel 的 Controller 做 Unit Test

    之前尝试过对 Laravel 的 Controller 做 Feature Test,但是在业务变得越来越复杂之后,我感觉对 controller 里的函数也没了自信,急需对功能函数做 Unit Te ...

  6. MySQL----数据库操作2

    数据库高级操作: SHOW DATABASES; 显示数据库 CREATE DATABASE 数据库名称 DEFAULT CHARSET utf8 COLLATE utf8_general_ci CR ...

  7. python 全栈开发,Day108(客户管理之权限控制,客户管理之动态"一级"菜单,其他应用使用rbac组件,django static文件的引入方式)

    一.客户管理之权限控制 昨天的作业,有很多不完善的地方 下载代码,基本实现权限验证 https://github.com/987334176/luffy_permission/archive/v1.2 ...

  8. hdu 1005 根据递推公式构造矩阵 ( 矩阵快速幂)

    f(1) = 1, f(2) = 1, f(n) = (A * f(n - 1) + B * f(n - 2)) mod 7. Sample Input1 1 3 //a b n1 2 100 0 0 ...

  9. java快速排序引起的StackOverflowError异常

    写在前面:这篇随笔主要记录一下递归调用引起的虚拟机栈溢出的情况以及通过参数配置了虚拟机栈大小来使递归调用可以顺利执行.并没有对涉及到的一些概念进行详细的解释(因为我自己目前对这些概念并不是特别清楚), ...

  10. 设置JAVA环境变量

    export JAVA_HOME=/usr/local/jdkexport JRE_HOME=/usr/local/jdk/jreexport CLASSPATH=.:$JAVA_HOME/lib/d ...