java多线程同步机制AQS解读

↘锁芯ラ 提交于 2020-02-08 02:53:46

java多线程同步机制AQS解读

一、简介

AQS(AbstractQueuedSynchronizer,同步器)是java中同步机制的基框架,jdk中的常用同步类如ReentrantLock、ReentrantReadWriteLock、CountDownLatch、Semaphore等都是基于AQS同步器实现的,这里结合源码对AQS进行解读,加深对jdk同步类的理解。

二、AQS核心知识点

2.1 AQS同步器类定义

AQS即AbstractQueuedSynchronizer类,是一个抽象类,类定义如下:

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
//具体代码在此省略,关键代码后续介绍
}

其中继承的抽象类AbstractOwnableSynchronizer源码如下:

package java.util.concurrent.locks;
//一个同步器可能是被一个线程独立占有的, AbstractOwnableSynchronizer提供了创建锁和相关同步器的基本定义。
public abstract class AbstractOwnableSynchronizer
    implements java.io.Serializable {
    private static final long serialVersionUID = 3737899427754241961L;
    //空构造器
    protected AbstractOwnableSynchronizer() { }
    //独占模式下的拥有者
    private transient Thread exclusiveOwnerThread;
    //设置指定线程拥有独立访问权限
    protected final void setExclusiveOwnerThread(Thread thread) {
        exclusiveOwnerThread = thread;
    }
    //返回拥有独立访问权限的线程,没有则返回null
    protected final Thread getExclusiveOwnerThread() {
        return exclusiveOwnerThread;
    }
}

AbstractOwnableSynchronizer中的方法在独占模式下,当同步状态获取成功时会被调用。

2.2 核心实现

AQS同步器是基于FIFO(first-in-first-out)同步等待队列和同步状态(单个原子int变量)实现的,注意它没有实现任何同步接口。AQS有独占式和共享式两种模式,独占式同一时刻只能一个线程获取锁,而共享式同一时刻可以有多个线程获取锁。同步状态获取成功时,直接返回,否则线程进入同步队列,等待下次尝试获取。

2.2.1 FIFO同步队列
2.2.1.1 同步队列解释

同步队列是一个双向链表,也叫CHL(Craig、Landin、Hagersten)队列,由结点Node构成。当线程获取同步状态失败时,同步器将线程及等待状态等信息封装在一个Node结点里,并插入到同步队列尾部,同时线程阻塞。当同步状态释放时,同步器会唤醒同步队列首结点,让其参与尝试获取同步状态。同步器有head结点和tail结点,关联着同步队列,结构示意如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VnHOCdNH-1581082154873)(http://www.plantuml.com/plantuml/png/IojAp4rLU3gXvzdQfKzdhgZcKW22w8pKn9HO2BCaCJCdbgkMYoiDIUNB6VEVTao7A8Q8Fzyz-NdJJaE845XZGL5gcM4iWf-NMb42vTTYQ3H2qsY4fj48bqPZD8t6Q1gDOHfhkK2UKj3LjOEvbGlaDIG1Oouki1kHX8p08aQeYi3kGp48R36ADW00)]

2.2.1.2 同步队列源码

Node结点源码如下:

package java.util.concurrent.locks;
import java.util.concurrent.TimeUnit;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import sun.misc.Unsafe;
public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {

    static final class Node {
        //共享模式下的Node结点
        static final Node SHARED = new Node();
        //独占模式下的Node结点
        static final Node EXCLUSIVE = null;

        //waitStatus值,线程处于取消状态,因同步队列中等待线程超时或被中断,将从同步队列中取消等待
        static final int CANCELLED =  1;
        //waitStatus值,当前结点获取同步状态,后继结点处于等待状态,如果当前结点线程释放同步状态或取消,则后继结点运行
        static final int SIGNAL    = -1;
        //waitStatus值,结点在等待队列中,当其它线程调用Condition的signal()方法后,结点由等待队列转到同步队列,参与同步状态获取
        static final int CONDITION = -2;
        //waitStatus值,表示下一次同步状态获取将无条件传播下去
        static final int PROPAGATE = -3;

        //结点等待状态
        volatile int waitStatus;
        //前驱结点
        volatile Node prev;
        //后继结点
        volatile Node next;
        //结点中的线程
        volatile Thread thread;
        //等待队列中的后继结点(处于condition或共享状态,共享模式时值为SHARED),
        Node nextWaiter;

        //等待结点是否是共享模式下的等待
        final boolean isShared() {
            return nextWaiter == SHARED;
        }

        //获取前驱结点
        final Node predecessor() throws NullPointerException {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }

        //初始化构造器
        Node() {    // Used to establish initial head or SHARED marker
        }
        //添加等待结点构造器
        Node(Thread thread, Node mode) {     // Used by addWaiter
            this.nextWaiter = mode;
            this.thread = thread;
        }
        //添加结点构造器
        Node(Thread thread, int waitStatus) { // Used by Condition
            this.waitStatus = waitStatus;
            this.thread = thread;
        }
    }
}

同步队列的尾结点添加是基于CAS实现的,依赖方法enq、compareAndSetHead、compareAndSetTail实现,源码如下:

    //添加结点入同步队列
    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

    //cas方式设置同步队列头结点
    private final boolean compareAndSetHead(Node update) {
        return unsafe.compareAndSwapObject(this, headOffset, null, update);
    }

    //cas方式添加同步队列尾结点
    private final boolean compareAndSetTail(Node expect, Node update) {
        return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
    }
2.2.1.3 同步队列安全保证

同步队列采用双向链表结构,结合CAS机制,保证了多线程下的安全,具体如下:

  • 获取时,由于队首只有一个,多线程下,每次也只能取第一个;
  • 插入时,采用CAS机制,只有在前一个结点确定的情况下才插入,从而保证插入是安全的;
2.2.2 同步状态
2.2.2.1 同步状态源码

同步器状态state管理的核心方法有getState、setState、compareAndSetState,源码及分析如下:

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
    //等待队列头结点,延迟加载,如果head结点存在,则它的waitStatus值不能是CANCELLED
    private transient volatile Node head;
    //等待队列尾结点,延迟加载,只能通过enq方法添加新的等待结点
    private transient volatile Node tail;
    //同步状态
    private volatile int state;

    //获取同步器当前同步状态
    protected final int getState() {
        return state;
    }

    //设置同步器当前状态
    protected final void setState(int newState) {
        state = newState;
    }

    //原子的设置同步状态,如果当前值等于期望值,则更新新值
    protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }
}
2.2.2.2 同步状态安全保证

同步状态使用int字段,添加volatile修饰,保证有序性和可见性,结合CAS机制,确保多线程下是安全的。

2.2.2.3 同步状态使用

同步状态是同步类的基础状态,对于共享模式和独占模式值的使用不一样:

  • 独占模式:同一时刻只有一个线程占有,1表示占有成功,0表示占有失败;
  • 共享模式:同一时刻可多个线程占有,大于0表示还可分配(可占有),小于等于0表示不可分配(占有失败);

2.3 同步实现

同步器使用了模板方法模式,子类通过继承AQS,重写受保护的改变同步状态的方法,进而实现所有的队列和阻塞机制,子类需实现的方法有tryAcquire、tryRelease、tryAcquireShared、tryReleaseShared、isHeldExclusively,源码定义如下:

//独占模式下尝试获取同步状态
protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}
//独占模式下尝试释放同步状态
protected boolean tryRelease(int arg) {
    throw new UnsupportedOperationException();
}
//共享模式下尝试获取同步状态
protected int tryAcquireShared(int arg) {
    throw new UnsupportedOperationException();
}
//独占模式下尝试释放同步状态
protected boolean tryReleaseShared(int arg) {
    throw new UnsupportedOperationException();
}
//同步器是否被当前线程独占模式占有
protected boolean isHeldExclusively() {
    throw new UnsupportedOperationException();
}

2.4 使用方式

AQS使用了模板方法模式,子类通过继承AQS,重写受保护的改变同步状态的方法,进而实现所有的队列和阻塞机制。同时子类应该是同步类内部的非public的帮助类,用于实现同步类的同步特性。

定义同步类A
同步类内部定义继承自同步器的子类Sync
子类Sync重写同步器的tryAcquire等方法及定义同步方法
同步类A定义同步方法及内部调用Sync同步方法
调用同步器的各类同步方法

三、示例

这里以ReentrantLock加锁为例,走下整个流程。

3.1 调用示例

ReentrantLock加锁调用代码很简单,如下:

Lock lock = new ReentrantLock();
lock.lock();

3.2 调用流程

Created with Raphaël 2.2.0start初始化锁:Lock lock = new ReentrantLock()内部默认初始化非公平锁:sync = new NonfairSync()加锁调用:lock.lock()内部加锁调用:sync.lock()内部获取同步状态:compareAndSetState(0, 1)End再次独占方式获取同步状态:acquire(1)尝试获取同步状态:tryAcquire(arg),失败则进入同步队列acquireQueued(addWaiter(Node.EXCLUSIVE), arg)yesno

四、备注

这里介绍AQS同步器的关键代码、说明及示例,更多详情请查看jdk相关源码。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!