Java 并发类库AbstractQueuedSynchronizer 分析(一)

2014-11-24 02:40:47 · 作者: · 浏览: 5

1. AQS简介

AQS是Java并发类库的基础,其提供了一个基于FIFO队列,可以用于构建锁或者其他相关同步装置的基础框架。该同步器(以下简称同步器)利用了一个int来表示状态,期望它能够成为实现大部分同步需求的基础。使用的方法是继承,子类通过继承同步器并需要实现它的方法来管理其状态,管理的方式就是通过类似acquire和release的方式来操纵状态。然而多线程环境中对状态的操纵必须确保原子性,因此子类对于状态的把握,需要使用这个同步器提供的以下三个方法对状态进行操作:

  • java.util.concurrent.locks.AbstractQueuedSynchronizer.getState()
  • java.util.concurrent.locks.AbstractQueuedSynchronizer.setState(int)
  • java.util.concurrent.locks.AbstractQueuedSynchronizer.compareAndSetState(int, int)

    子类推荐被定义为自定义同步装置的内部类,同步器自身没有实现任何同步接口,它仅仅是定义了若干acquire之类的方法来供使用。该同步器即可以作为排他模式也可以作为共享模式,当它被定义为一个排他模式时,其他线程对其的获取就被阻止,而共享模式对于多个线程获取都可以成功。

    同步器是实现锁的关键,利用同步器将锁的语义实现,然后在锁的实现中聚合同步器。可以这样理解:锁的API是面向使用者的,它定义了与锁交互的公共行为,而每个锁需要完成特定的操作也是透过这些行为来完成的(比如:可以允许两个线程进行加锁,排除两个以上的线程),但是实现是依托给同步器来完成;同步器面向的是线程访问和资源控制,它定义了线程对资源是否能够获取以及线程的排队等操作。锁和同步器很好的隔离了二者所需要关注的领域,严格意义上讲,同步器可以适用于除了锁以外的其他同步设施上(包括锁)。

    2. CLH算法

    锁的实现是以CLH算法为基础。下面简单介绍一下CLH算法: CLH算法构建了隐式的链表,是一种非阻塞算法的实现。CLH队列中的结点QNode中含有一个locked字段,该字段若为true表示该线程需要获取锁,且不释放锁,为false表示线程释放了锁。结点之间是通过隐形的链表相连,之所以叫隐形的链表是因为这些结点之间没有明显的next指针,而是通过myPred所指向的结点的变化情况来影响myNode的行为。CLHLock上还有一个尾指针,始终指向队列的最后一个结点。CLHLock的类图如下所示: vcewx/e94bXjys23xcv4oaO1sdK7uPbP37PM0OjSqsrNt8XL+Mqxo6y9q7Wxx7C94bXjtcRsb2NrZWTT8sno1sPOqmZhbHNlo6zNrMqxu9jK1cewx/e94bXjoaPI58/CzbzL+cq+o6zP37PMQdDo0qq78cihy/ijrMbkbXlOb2Rl0/LOqnRydWWjrNCpyrF0YWls1rjP8s/fs8xBtcS94bXjo6zIu7rzz9+zzELSsrzTyOu1vc/fs8xBuvPD5qOsdGFpbNa4z/LP37PMQrXEveG146GjyLu688/fs8xBus1CtrzU2sv8tcRteVByZWTT8snP0P3XqqOs0rvBv8v8tcRteVByZWS94bXjtcRsb2NrZWTX1rbOseTOqmZhbHNlo6zL/L7Nv8nS1LvxyKHL+Mmo0NCho8P3z9TP37PMQbXEbXlQcmVkCiBsb2NrZWTT8s6qZmFsc2WjrLTLyrHP37PMQbvxyKG1vcHLy/ihowo8aW1nIHNyYz0="https://www.cppentry.com/upload_files/article/76/1_tu6ke__.jpg" width="500" height="400" alt="\">
    整个CLH的代码如下,其中用到了ThreadLocal类,将QNode绑定到每一个线程上,同时用到了AtomicReference,对尾指针的修改正是调用它的getAndSet()操作来实现的,它能够保证以原子方式更新对象引用。 CLH算法的示意代码如下:
    import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
    
    public class CLHLock {
        public static class CLHNode {
            private boolean isLocked = true; // 默认是在等待锁
        }
    
        @SuppressWarnings("unused" )
        private volatile CLHNode tail ;
        private static final AtomicReferenceFieldUpdater
        
          UPDATER = AtomicReferenceFieldUpdater
                      . newUpdater(CLHLock.class, CLHNode .class , "tail" );
    
        public void lock(CLHNode currentThread) {
            CLHNode preNode = UPDATER.getAndSet( this, currentThread);
            if(preNode != null) {//已有线程占用了锁,进入自旋
                while(preNode.isLocked ) {
                }
            }
        }
    
        public void unlock(CLHNode currentThread) {
            // 如果队列里只有当前线程,则释放对当前线程的引用(for GC)。
            if (!UPDATER .compareAndSet(this, currentThread, null)) {
                // 还有后续线程
                currentThread. isLocked = false ;// 改变状态,让后续线程结束自旋
            }
        }
    }
        
    至于AQS的实现,和CLH略有不同,同步器的开始提到了其实现依赖于一个FIFO队列,那么队列中的元素Node就是保存着线程引用和线程状态的容器,每个线程对同步器的访问,都可以看做是队列中的一个节点。Node的主要包含以下成员变量:
    Node {
        int waitStatus;
        Node prev;
        Node next;
        Node nextWaiter;
        Thread thread;
    }

    成员变量主要负责保存该节点的线程引用,同步等待队列(以下简称sync队列)的前驱和后继节点,同时也包括了同步状态。节点成为sync队列和condition队列构建的基础,在同步器中就包含了sync队列。同步器拥有三个成员变量:sync队列的头结点head、sync队列的尾节点tail和状态state。对于锁的获取,请求形成节点,将其挂载在尾部,而锁资源的转移(释放再获取)是从头部开始向后进行。对于同步器维护的状态state,多个线程对其的获取将会产生一个链式的结构。
    \

    3.AQS实现分析

    3.1 概述

    同步器的设计包含获取和释放两个操作:
    获取操作过程如下:
    if(尝试获取成功){
        return;
     }else{
         加入等待队列;park自己
    }

    释放操作: