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. hping网络安全工具的安装及使用

    hping是用于生成和解析TCPIP协议数据包的开源工具.创作者是Salvatore Sanfilippo.目前最新版是hping3,支持使用tcl脚本自动化地调用其API.hping是安全审计.防火 ...

  2. python的MD5

    import hashlib def md5(str0): hl = hashlib.md5()# 创建md5对象 hl.update(str0.encode(encoding='utf-8'))#此 ...

  3. Android 自定义View二(深入了解自定义属性attrs.xml)

    1.为什么要自定义属性 要使用属性,首先这个属性应该存在,所以如果我们要使用自己的属性,必须要先把他定义出来才能使用.但我们平时在写布局文件的时候好像没有自己定义属性,但我们照样可以用很多属性,这是为 ...

  4. 改变input标签中placeholder显示的颜色

    ::-webkit-input-placeholder { /* WebKit browsers */ color: #A9A9A9; } :-moz-placeholder { /* Mozilla ...

  5. SQL中的 if 结构和循环(while)结构

  6. 1. let 和 const 命令

    一.简单认识 1. 用let来声明变量,变量作用域就在{}(块级作用域)中 2. 用const声明变量,变量值不可更改 3. 增加了let以后,在声明变量时应该多考虑一下变量的用途,是否希望只在当前代 ...

  7. 【python】已安装模块提示ImportError: No module named

    今天遇到了一个问题,运行代码时出现错误 Traceback (most recent call last): File , in <module> import zmq ImportErr ...

  8. (四)CXF处理JavaBean以及复合类型

    前面讲的是处理简单类型,今天这里来讲下CXF处理JavaBean以及复合类型,比如集合: 这里实例是客户端传一个JavaBean,服务器端返回集合类型: 在原来的项目实例基础上,我们先创建一个实体类U ...

  9. pytest十一:函数传参和 firture 传参数 request

    为了提高代码的复用性,我们在写用例的时候,会用到函数,然后不同的用例去调用这个函数.比如登录操作,大部分的用例都会先登录,那就需要把登录单独抽出来写个函数,其它用例全部的调用这个登录函数就行.但是登录 ...

  10. centos下配置DNS

    centos网络配置实例 1,配置DNSvi /etc/resolv.conf加入: 代码如下: nameserver 192.168.0.1 nameserver 8.8.8.8 nameserve ...