JDK中AbstractQueuedSynchronizer应用解析

这个类首先是一个抽象类,定义了一个模板,很多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字段,那么是如何保存读写的数量和写锁的数量呢?
  • 对于读锁,如何知道当前自己持有多少个单位的锁呢

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

时间: 2025-01-02 05:47:54

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

浅析JDK中ServiceLoader的源码

前提 紧接着上一篇<通过源码浅析JDK中的资源加载>,ServiceLoader是SPI(Service Provider Interface)中的服务类加载的核心类,也就是,这篇文章先介绍ServiceLoader的使用方式,再分析它的源码. ServiceLoader的使用 这里先列举一个经典的例子,MySQL的Java驱动就是通过ServiceLoader加载的,先引入mysql-connector-java的依赖: <dependency> <groupId>m

转:在java中使用dom4j解析xml

在java中使用dom4j解析xml 虽然Java中已经有了Dom和Sax这两种标准解析方式 但其操作起来并不轻松,对于我这么一个初学者来说,其中部分代码是活生生的恶心 为此,伟大的第三方开发组开发出了Jdom和Dom4j等工具 鉴于目前的趋势,我们这里来讲讲Dom4j的基本用法,不涉及递归等复杂操作 Dom4j的用法很多,官网上的示例有那么点儿晦涩,这里就不写了 首先我们需要出创建一个xml文档,然后才能对其解析 xml文档: <?xml version="1.0" encod

HTTP1.1中CHUNKED编码解析(转载)

HTTP1.1中CHUNKED编码解析 一般HTTP通信时,会使用Content-Length头信息性来通知用户代理(通常意义上是浏览器)服务器发送的文档内容长度,该头信息定义于HTTP1.0协议RFC  1945  10.4章节中.浏览器接收到此头信息后,接受完Content-Length中定义的长度字节后开始解析页面,但如果服务端有部分数据延迟发送吗,则会出现浏览器白屏,造成比较糟糕的用户体验. 解决方案是在HTTP1.1协议中,RFC  2616中14.41章节中定义的Transfer-E

jdk 中Runtime之单例模式 学习

这段代码是我从源码中截取的,大家很容易看到currentRuntime是一个静态变量,getRunTime对应的就是getInstacne.不是说这种方法不好吗? 1 public class Runtime { 2 45 private static Runtime currentRuntime = new Runtime(); 3 4 5 Returns the runtime object associated with the current Java application. Most

JDK中的Timer和TimerTask详解

目录结构: Timer和TimerTask 一个Timer调度的例子 如何终止Timer线程 关于cancle方式终止线程 反复执行一个任务 schedule VS. scheduleAtFixedRate 一些注意点 1. Timer和TimerTask Timer是jdk中提供的一个定时器工具,使用的时候会在主线程之外起一个单独的线程执行指定的计划任务,可以指定执行一次或者反复执行多次. TimerTask是一个实现了Runnable接口的抽象类,代表一个可以被Timer执行的任务. 2.

WebService之JDK中wsimport命令

1.编写WebService类,使用@WebService注解 package test; import javax.jws.WebService; @WebService public class HelloServiceImpl{ public String say(String name) { return "Hello "+name; } } WebService类 2.使用main方法发布WebService package test; import javax.xml.ws

Android中XML数据解析

转载请注明出处:http://blog.csdn.net/yegongheng/article/details/38296207 XML初步 今天我们来学习另一种非常重要的数据交换格式-XML.XML(Extensible Markup Language的缩写,意为可扩展的标记语言),它是一种元标记语言,即定义了用于定义其他特定领域有关语义的.结构化的标记语言,这些标记语言将文档分成许多部件并对这些部件加以标识.XML 文档定义方式有:文档类型定义(DTD)和XML Schema.DTD定义了文

Jdk中的设计模式

转自:http://blog.csdn.net/gtuu0123/article/details/6114197 本文主要是归纳了JDK中所包含的设计模式,包括作用和其设计类图.首先来个总结,具体的某个模式可以一个一个慢慢写,希望能对研究JDK和设计模式有所帮助. 一.设计模式是什么(1)反复出现问题的解决方案(2)增强软件的灵活性(3)适应软件不断变化 二.学习JDK中设计模式的好处(1)借鉴优秀代码的设计,有助于提高代码设计能力(2)JDK的设计中体现了大多数设计模式,是学习设计模式的较好的

Android中使用Gson解析JSON数据的两种方法

Json是一种类似于XML的通用数据交换格式,具有比XML更高的传输效率;本文将介绍两种方法解析JSON数据,需要的朋友可以参考下 Json是一种类似于XML的通用数据交换格式,具有比XML更高的传输效率. 从结构上看,所有的数据(data)最终都可以分解成三种类型: 第一种类型是标量(scalar),也就是一个单独的字符串(string)或数字(numbers),比如"北京"这个单独的词. 第二种类型是序列(sequence),也就是若干个相关的数据按照一定顺序并列在一起,又叫做数组