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. ORACLE 利用SCN恢复误delete的表

    --kg是误删除的表 SQL> select count(*) from kg;   COUNT(*) ----------     820861 SQL> delete from kg; ...

  2. zabbix3.0.4使用shell脚本和zabbix自带模板两种方法添加对指定进程和端口的监控

    zabbix3.0.4添加对进程的监控: 方法一:通过自定义命令进行监控 主要思路: 通过 ps -ef|grep sdk-push-1.0.0.jar |grep -v grep|wc -l 这个命 ...

  3. 初识numpy

    from numpy import *   导入numpy包 random可以生成随机数组 通过mat函数,将数组转换成矩阵,可以对矩阵进行求逆计算等.其中.I操作实现了矩阵求逆计算操作. 执行矩阵乘 ...

  4. Expm 7_1树中的最大独立集问题

    [问题描述] 给定一个无回路的无向图(即树),设计一个动态规划算法,求出该图的最大独立集,并输出该集合中的各个顶点值. package org.xiu68.exp.exp7; import java. ...

  5. ubuntu系统下Python虚拟环境的安装和使用

    ubuntu系统下Python虚拟环境的安装和使用        前言:进行python项目开发的时候,由于不同的项目需要使用不同的资源包和相关的配置,因此创建多个python虚拟环境,在虚拟环境下开 ...

  6. 实现自己的Koa2

    这部分的代码在https://github.com/zhaobao1830/koa2中demo文件夹中 Koa就是基于node自带的http模块,经过封装,监听端口,实现ctx(上下文)管理,中间件管 ...

  7. 中文多分类 BERT

    直接把自己的工作文档导入的,由于是在外企工作,所以都是英文写的 Steps: git clone https://github.com/google-research/bert prepare dat ...

  8. 使用mybatis-spring-boot-starter如何打印sql语句

    只需要将接口文件的日志设置为debug即可. 例如你的mapper接口所在的文件夹是 com.demo.mapper 那么在application.properties配置文件中添加 logging. ...

  9. js面向对象 下

    // 声明对象的方法 一  (通过常量直接赋值) var xiaoming = {//声明一个对象(直接通过一个常量xiaoming用{}赋值的方式声明) name: '小明', //对象的一个属性 ...

  10. linux命令: chown命令

    chown将指定文件的拥有者改为指定的用户或组,用户可以是用户名或者用户ID:组可以是组名或者组ID:文件是以空格分开的要改变权限的文件列表,支持通配符.系统管理员经常使用chown命令,在将文件拷贝 ...