这个类首先是一个抽象类,定义了一个模板,很多java同步相关的类(ReetrantLock、Semaphore、CountDownLatch等)都是基于AbstractQueuedSynchronizer来实现的

AbstractQueuedSynchronizer

本身就是一个链表,提供的线程安全的操作。核心思想是通过CAS插入链表的尾部和获取链表的头结点。算法暂时先不谈,先说说他的应用,主要下先说说他的一些模板方法。

AbstractQueuedSynchronizer#acquire()

public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}

其中tryAcquire()就是一个模板方法,当tryAcquire()返回false的时候,就会把当前线程封装为Node(链表的数据结构),然后挂起(使用LockSuport.park())当前线程。

其实在从队列中获取头节点的时候并恢复的时候,也会调用tryAcquire()方法,因为对一些非公平锁可能被强占。

AbstractQueuedSynchronizer#release()

和acquire()对应的就是release()

public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}

tryRelease()也是一个模板方法,由子类自己去实现。如果tryRelease()返回true的话,就会从链表中获取头节点(如果有),并恢复线程。 因此,java并发包里很多都会用到这个来进行自定义。下面说举一个例子说说明如何利用上面2个模板方法来实现并发的

ReentrantLock

ReentrantLock的并发控制是通过继承AbstractQueuedSynchronizer()来实现并发的。

abstract static class Sync extends AbstractQueuedSynchronizer{
...
}

  

而公平锁和非公平锁又是继承了Sync, 以默认的非公平锁分析,首先来tryAcquire(),通过注释可以看出基本步骤,如果要实现公平锁也很简单,在当前线程没有占有的情况下,多增加一个判断链表是否有等待的线程即可

protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
} final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 0 表示当前没有其他线程占用
if (c == 0) {
//通过cas的方式来设置state,如果设置成功,则认为当前线程可以获取锁,cas失败则任务其他线程比他快一步占用
if (compareAndSetState(0, acquires)) {
//设置当前线程独占
setExclusiveOwnerThread(current);
//返回true,即不需要放入等待的链表中
return true;
}
}
//如果当前线程本来就占有锁
else if (current == getExclusiveOwnerThread()) {
//占用的次数+1,这也是为什么 lock()和unlock()需要一一对应
int nextc = c + acquires;
//说明同一个线程lock()的次数为int的最大值
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
// 设置state,返回获取锁成功
setState(nextc);
return true;
}
return false;
}

  

再来看看tryRelease()

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

  

因为释放锁的时候并没有并发情况,只要保证当state为0的时候,设置独占锁为null并返回true即可。代码很简单。

小结

有人看到这里觉得,ReentrantLock是通过lock()和unlock()方法来加锁解锁的,和你说的acquire()和release()有什么关系呢?其实lock()的核心就是调用acquire(1),unlock()就是调用release(1)

AbstractQueuedSynchronizer#acquireShared()

acquireShared()和acquire()类似,只不过在调用的是

public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}

  

tryAcquireShared()是模板方法,可以简单的理解为是把boolean类型换位int类型的acquire()

AbstractQueuedSynchronizer#releaseShared()

和acquireShared()对应acquire(),releaseShared()就是对应release(),就不在赘述

public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}

  

下面再举个例子说明acquireShared()和releaseShared()的使用

Semaphore

Semaphore就是允许指定数量的线程进行入临界区。看看是什么实现,其实Semaphore的内部结构和ReentrantLock差不多,主要就是看看他是如何利用AbstractQueuedSynchronizer来实现并发的。首先看看如何利用tryAcquireShared()控制并发线程数的。

protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
} final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}

  

是不是很简单,就是利用CAS来改变available值来控制,而available就是我们自己定义的能够进入临界区的最大线程数 那么releaseShared()是怎么实现的,其实简单一想也应该是增加available值

protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}

 

## 小节
可以看出AbstractQueuedSynchronizer主要用分为共享锁和独占锁,对于共享锁就像是一个计数器,每次获取锁的时候计数器-1,释放的时候计数器+1。

ReentrantReadWriteLock

ReentrantReadWriteLock是读写锁,每个读锁之间是不竞争锁的,但读锁和写锁、写锁和写锁之间是竞争关系。这个并发类就是综合利用上面所说的内容。首先看一下内部结构

private final ReentrantReadWriteLock.ReadLock readerLock;

private final ReentrantReadWriteLock.WriteLock writerLock;

final Sync sync;

  

成员变量分别是读锁和写锁(内部类)和一个继承了AbstractQueuedSynchronizer的Sync类,如果理解了上面的ReentrantLock和Semaphore,我们自己进行推敲一下,应该是读锁应该是和Semaphore类似,而写锁应该是和ReentrantLock类似。虽然这么说,还是有许多细节需要注意的:

  • 如果保证非公平锁下,写锁不会饿死?
  • 一个AbstractQueuedSynchronizer只有一个state字段,那么是如何保存读写的数量和写锁的数量呢?
  • 对于读锁,如何知道当前自己持有多少个单位的锁呢

上面都是需要在设计读写锁需要考虑的地方,相信你看了源码就会知道答案!

JDK中AbstractQueuedSynchronizer应用解析的更多相关文章

  1. JDK中线程池参详细解析

    在jdk中为我们提供了三种创建线程池的方式,但是在阿里的编码规范里面都是明确禁止使用这三种api去创建线程池,推荐我们去自定义线程池.为什么? 要回答为什么,我们需要明白创建线程池时,各参数的作用: ...

  2. Lock和Condition在JDK中LinkedBlockingQueue的应用

    Lock和Condition在JDK中LinkedBlockingQueue的应用,核心源码注释解析如下: import java.util.concurrent.LinkedBlockingQueu ...

  3. Spring 中参数名称解析 - ParameterNameDiscoverer

    Spring 中参数名称解析 - ParameterNameDiscoverer Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.ht ...

  4. java基础74 XML解析中的SAX解析相关知识点(网页知识)

    1.SAX解析工具 SAX解析工具:是Sun公司提供的,内置JDK中.org.xml.sax.*         点击查看: DOM解析相关知识:以及DOM和SAX解析的原理(区别) 2.SAX解析的 ...

  5. 沉淀再出发:java中线程池解析

    沉淀再出发:java中线程池解析 一.前言 在多线程执行的环境之中,如果线程执行的时间短但是启动的线程又非常多,线程运转的时间基本上浪费在了创建和销毁上面,因此有没有一种方式能够让一个线程执行完自己的 ...

  6. 浅析JDK中ServiceLoader的源码

    前提 紧接着上一篇<通过源码浅析JDK中的资源加载>,ServiceLoader是SPI(Service Provider Interface)中的服务类加载的核心类,也就是,这篇文章先介 ...

  7. JDK中注解的底层实现

    前提 用Java快三年了,注解算是一个常用的类型,特别是在一些框架里面会大量使用注解做组件标识.配置或者策略.但是一直没有深入去探究JDK中的注解到底是什么,底层是怎么实现了?于是参考了一些资料,做了 ...

  8. Java 9的JDK中值得期待的:不仅仅是模块化

    在多次延期后,Java 9将于9月21日以Java开发工具包9的形式出现,这是自2014年3月以来,Java标准版的第一次重大升级.官方列出了JDK 9的大约90个新特性,模块化是最主要的一个.将Ja ...

  9. 曹工力荐:调试 jdk 中 rt.jar 包部分的源码(可自由增加注释,修改代码并debug)

    背景 大家知道,jdk安装的目录下,一般会有个src.zip包,这个包基本对应了rt.jar这个包.rt.jar这个包里面,就放了jdk中,jdk采用java实现的那部分类库代码,比如java.lan ...

随机推荐

  1. day7、用户登陆出现-bash-4.1$错误的原因

    有时候在使用用户登陆Linux系统时会出现-bash-4.1$错误,不显示用户名,路径信息. 原因:用户家目录里面与环境变量有关的文件被删除所导致的 .bash_profile .bashrc 这两个 ...

  2. SSIS中循环遍历组件[Foreach Loop Container]

    背景 每月给业务部门提取数据,每个分公司都要提取一般,先跑SQL,再粘贴到Excel中,然后发邮件给相关的人员.费时费力,还容易粘贴错位.因此,需要通过一个程序完成这些步骤.我首先想到的是通过SSIS ...

  3. NYOJ127 星际之门(一)(最小生成数的个数+高速幂)

    题目描写叙述: http://acm.nyist.net/JudgeOnline/problem.php?pid=127 能够证明.修建N-1条虫洞就能够把这N个星系连结起来. 如今.问题来了.皇帝想 ...

  4. Dreamweaver CS5 CS6 代码格式化、美化插件(可同一时候格式化HTML、JavaScript、CSS )眼下最好用的代码格式化扩展

    Dreamweaver CS5 CS6 代码格式化.美化插件(可同一时候格式化HTML.JavaScript.CSS )眼下最好用的代码格式化扩展. 众所周知,Dreamweaver CS5 CS6 ...

  5. Android进程间通信与数据共享(ppt)

  6. 《跟我学IDEA》四、配置模板(提高代码编写效率)

    上一篇博文,我们学习了idea的一些实用配置,相信大家也对idea这个开发工具有了一个大概的了解.今天我们来学习模板的配置,idea提供很多模板从而提高编写代码的效率,比如说一些经常用的代码及生成文件 ...

  7. java实现播放mp3功能

    1.首先引入jlayer.jar <!-- https://mvnrepository.com/artifact/javazoom/jlayer --> <dependency> ...

  8. sqlser 2005 使用执行计划来优化你的sql

    一:sqlserver 执行计划介绍    sqlserver 执行计是在sqlser manager studio 工具中打开,是检查一条sql执行效率的工具.建议配合SET STATISTICS ...

  9. DotNetCasClient 如何获取Cas服务器返回的attributes中的数据

    最近开始接触做与其它认证系统的集成,其中有个是与某学校的CAS服务器集成.cas服务器认证成功后返回的数据格式如下: 其中红色部分是我需要取出来用于识别用户身份的数据. 一开始,我根据网上的教程,引用 ...

  10. 对Java中多态,封装,继承的认识(重要)

                                                            一.Java面向对象编程有三大特性:封装,继承,多态 在了解多态之前我觉得应该先了解一下 ...