AQS与ReentrantLock源码分析

AQS

overview

AQS的全程为 AbstractQueuedSynchronizer,位于java.util.concurrent.locks

AQS 就是一个抽象类,主要用来构建锁和同步器。

public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
}

AQS 为构建锁和同步器提供了一些通用功能的是实现,因此,使用 AQS 能简单且高效地构造出应用广泛的大量的同步器,比如 ReentrantLockSemaphore,其他的诸如 ReentrantReadWriteLockSynchronousQueueFutureTask(jdk1.7) 等等皆是基于 AQS 的。

AQS框架图:

 

  • 上图中有颜色的为Method,无颜色的为Attribution。

  • 总的来说,AQS框架共分为五层,自上而下由浅入深,从AQS对外暴露的API到底层基础数据。

  • 当有自定义同步器接入时,只需重写第一层所需要的部分方法即可,不需要关注底层具体的实现流程。当自定义同步器进行加锁或者解锁操作时,先经过第一层的API进入AQS内部方法,然后经过第二层进行锁的获取,接着对于获取锁失败的流程,进入第三层和第四层的等待队列处理,而这些处理方式均依赖于第五层的基础数据提供层。

AQS原理

AQS 核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制 AQS 是用 CLH 队列锁实现的,即将暂时获取不到锁的线程加入到队列中。

CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS 是将每条请求共享资源的线程封装成一个 CLH 锁队列的一个结点(Node)来实现锁的分配。

AQS 原理图:

 

AQS 使用一个 int 成员变量来表示同步状态,通过内置的 FIFO 队列来完成获取资源线程的排队工作。AQS 使用 CAS 对该同步状态进行原子操作实现对其值的修改。

AQS 数据结构

AQS 基本的数据结构Node:

 

方法和属性值含义
waitStatus当前节点在队列中的状态
thread表示处于该节点的线程
prev前驱指针
predecessor返回前驱节点,没有的话抛出Null Pointer Exception
nextWaiter指向下一个处于CONDITION状态的节点
next后继指针

线程两种锁的模式:

模式含义
SHARED表示线程以共享的模式等待锁
EXCLUSIVE表示线程正在以独占的方式等待锁

waitStatus有下面几个枚举值:

枚举含义
0当一个Node被初始化的时候的默认值
CANCELLED为1,表示线程获取锁的请求由于中断或超时取消了
CONDITION为-2,表示当前节点在等待condition,也就是在condition队列中(满足condition后状态被修改为0)
PROPAGATE为-3,当前线程处在SHARED情况下,该字段才会使用
SIGNAL为-1,当前节点的下一个节点被阻塞(或即将被阻塞),因此当前节点释放锁或取消申请锁时必须对下一个节点发送 park 唤醒信号。为了避免竞争,acquire方法必须首先表明它需要一个signal,然后尝试acquire,然后获取失败后阻塞。

同步状态 state

private volatile int state;//共享变量,使用volatile修饰保证线程可见性
​
//返回同步状态的当前值
protected final int getState() {
    return state;
}
//设置同步状态的值
protected final void setState(int newState) {
    state = newState;
}
//原子地(CAS操作)将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值)
protected final boolean compareAndSetState(int expect, int update) {
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

通过修改State字段表示的同步状态可以实现多线程的独占模式和共享模式(加锁过程)

 

 

ReentrantLock

可重入锁:某个线程已经获得某个锁,可以再次获取锁而不会出现死锁(同一个线程持有锁的前提下再次申请当前锁),synchronized 和 Lock 均为可重入锁。

可中断锁:可中断锁就是可以响应中断的锁。在Java中,synchronized不是可中断锁,而Lock是可中断锁。如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,对于 synchronized,B只能一直阻塞等待,对于 Lock,B可以在超时后中断对共享资源的等待过程并执行其他代码。

公平锁:公平锁即尽量保证线程以请求锁的顺序来获取锁。比如,多个线程等待一个锁,当这个锁被释放时,等待时间最久的线程会获得该锁。而非公平锁则无法保证锁的获取按照请求锁的顺序进行,可能导致某个或者一些线程永远获取不到锁,synchronized 为非公平锁。

Java5可以通过显式定义同步锁对象来实现同步,由Lock对象充当同步锁。

Lock是控制多个线程对共享资源进行访问的工具。通常锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象(有些锁,如ReadWriteLock(读写锁)允许对共享资源并发访问)。Lock、ReadWriteLock是Java5提供的两个根接口,并为Lock提供了ReentrantLock(可重入锁)实现类,为ReadWriteLock提供了ReentrantReadWriteLock实现类。

ReentrantLocksynchronized的区别:

demo:

// **************************Synchronized的使用方式**************************
// 1.用于代码块
synchronized (this) {}
// 2.用于对象
synchronized (object) {}
// 3.用于方法
public synchronized void test () {}
// 4.可重入
for (int i = 0; i < 100; i++) {
	synchronized (this) {}
}
// **************************ReentrantLock的使用方式**************************
public void test () throw Exception {
	// 1.初始化选择公平锁、非公平锁
	ReentrantLock lock = new ReentrantLock(true);
	// 2.可用于代码块
    // 可重入性 获取两次锁
	lock.lock();
	try {
		try {
			// 3.支持多种加锁方式,比较灵活; 具有可重入特性
			if(lock.tryLock(100, TimeUnit.MILLISECONDS)){ }
		} finally {
			// 4.手动释放锁
			lock.unlock()
		}
	} finally {
		lock.unlock();
	}
}

ReentrantLock锁具有可重入性,也就是说,一个线程可以对已被加锁的ReentrantLock 锁再次加锁,ReentrantLock对象会维持一个计数器来追踪lock()方法的嵌套调用,线程在每次调用lock()加锁后,必须显式调用unlock()来释放锁,所以一段被锁保护的代码可以调用另一个被相同锁保护的方法。

ReentrantLock锁的构造方法中不传入参数或传入true时,产生公平锁,传入false时,产生非公平锁。

公平锁:以请求锁的顺序获得锁。

非公平锁:不保证锁的公平性,如synchronized。

Lock要求用户手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。

tryLock()tryLock(long time, TimeUnit unit)是比较常用的两个方法,并且体现了Lock的可中断性。

tryLock()方法具有返回值,它表示尝试获取锁,如果获取成功,则返回true;如果获取失败(即锁已被其他线程获取),则返回false,也就是说,这个方法无论如何都会立即返回(在拿不到锁时不会一直在那等待)。

tryLock(long time, TimeUnit unit)方法和tryLock()方法类似,只不过这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false,同时可以响应中断。如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。

一般情况下,通过tryLock来获取锁时是这样使用的:

Lock lock = ...;
if(lock.tryLock()) {
     try{
         //处理任务
     }catch(Exception ex){
		//处理异常
     }finally{
         lock.unlock();   //释放锁
     } 
}else {
    //如果不能获取锁,则直接做其他事情
}

Lock与synchronized的区别:

  1. synchronized是Java语言的关键字,因此是内置特性,Lock不是Java语言内置的,Lock是一个接口,通过实现类可以实现同步访问。

  2. synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行,Lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}

  3. 在资源竞争不是很激烈的情况下,synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态。

ReentrantLock源码分析

AQS提供了大量用于自定义同步器实现的Protected方法。自定义同步器实现的相关方法也只是为了通过修改State字段来实现多线程的独占模式或者共享模式。自定义同步器需要实现以下方法(ReentrantLock需要实现的方法如下,并不是全部):

方法名描述
protected boolean isHeldExclusively()该线程是否正在独占资源。只有用到Condition才需要去实现它。
protected boolean tryAcquire(int arg)独占方式。arg为获取锁的次数,尝试获取资源,成功则返回True,失败则返回False。
protected boolean tryRelease(int arg)独占方式。arg为释放锁的次数,尝试释放资源,成功则返回True,失败则返回False。
protected int tryAcquireShared(int arg)共享方式。arg为获取锁的次数,尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
protected boolean tryReleaseShared(int arg)共享方式。arg为释放锁的次数,尝试释放资源,如果释放后允许唤醒后续等待结点返回True,否则返回False。

一般来说,自定义同步器要么是独占方式,要么是共享方式,它们也只需实现tryAcquire-tryReleasetryAcquireShared-tryReleaseShared中的一种即可。AQS也支持自定义同步器同时实现独占和共享两种方式,如ReentrantReadWriteLockReentrantLock是独占锁,所以实现了tryAcquire-tryRelease

ReentrantLock中公平锁和非公平锁在底层是相同的。

1. 加锁

算法伪代码:

 源代码:

//根据传入参数初始化内部的sync为公平锁方式或非公平锁方式
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}
//调用内部类中的lock方法
public void lock() {
    sync.lock();
}

非公平锁:

// java.util.concurrent.locks.ReentrantLock

static final class NonfairSync extends Sync {
	...
	final void lock() {
        // 尝试快速CAS获取锁,失败后走正常流程,先检测state,根据state判断是否进行CAS
		if (compareAndSetState(0, 1))
			setExclusiveOwnerThread(Thread.currentThread());
		else
			acquire(1);
	}
  ...
}

公平锁:

// java.util.concurrent.locks.ReentrantLock

static final class NonfairSync extends Sync {
	...
	final void lock() {
        acquire(1);
    }
    ...
}

区别在于,非公平锁会直接尝试一次插队获取锁,非公平锁获取失败后,二者都进入 acquire 方法(AQS 中的一个模板方法)。

// java.util.concurrent.locks.AbstractQueuedSynchronizer

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

首先执行 tryAcquire 方法(区别在于公平锁会多一个检测当前节点是否是队首节点的检测)。

非公平锁:

// NonfairSync中
protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}
// NonfairSync的父类Sync中
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    // 锁未被占用,直接CAS尝试获取锁
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 获得锁的线程再次获取锁,修改state变量,表示重入
    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) {
        // 当前节点是队列中的队首节点时,CAS尝试获取锁
        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;
}

当获取锁失败后,会继续执行 acquire 方法:

// java.util.concurrent.locks.AbstractQueuedSynchronizer

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


// 向阻塞队列添加一个元素
private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        // CAS修改尾节点
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    //如果pred为null,或者CAS失败(存在竞争),执行enq
    enq(node);
    return node;
}

//enq
private Node enq(final Node node) {
    // 如果CAS失败,自旋CAS
    for (;;) {
        Node t = tail;
        if (t == null) { // Must initialize
            // 队列为空(队列初始化时才会执行),将tail和head初始化为一个头节点,再插入node
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            // CAS修改尾节点
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

自旋获取锁的过程:

// 对排队中的node执行获取锁的操作,直到获取成功或发生中断
// 为true表示存在需要取消加锁的节点,仅从这段代码可以看出,
// 除非发生异常,否则不会存在需要取消加锁的节点。
final boolean acquireQueued(final Node node, int arg) {
    // 标记是否成功获取到锁
    boolean failed = true;
    try {
        /**
    	 * interrupted表示在CLH队列的调度过程中,当前线程在休眠时,有没有被中断过
    	 */
        boolean interrupted = false;
        // 自旋
        for (;;) {
            // 获取前驱节点
            final Node p = node.predecessor();
            // 如果前驱为头节点,则尝试获取锁
            if (p == head && tryAcquire(arg)) {
                // 获取锁成功,头节点修改为当前节点
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            // 如果获取锁失败,或者当前节点的前驱不是头节点,判断是否应该挂起当前节点
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

private void setHead(Node node) {
    head = node;
    node.thread = null;
    node.prev = null;
}

// 返回当前线程是否应该被阻塞
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    // 获取前驱的节点状态
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        /*
         * 前驱节点在释放锁后会对当前节点发出signal,当前可以安全地执行park
         */
        return true;
    if (ws > 0) {
        /*
         * 如果前继节点状态为CANCELLED, 说明前驱节点已经取消,则需要通过回溯找到一个非CANCELLED状态有效节点值(值小于等于0)设置为前驱节点
         * 并返回false
         */
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        // 如果前继节点状态值为0或者共享锁PROPAGATE. 即前继节点为非SIGNAL或非CANCELLED, 则设置前继节点为SIGNAL,
        // 意味着前驱节点释放锁后需要向当前节点发出 park 唤醒信号
        // 并返回false(下一轮执行中,当前线程会被阻塞)
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

// 线程需要阻塞时,调用该方法挂起当前线程,然后返回当前线程的中断状态
private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

2. 解锁

伪代码:

 源代码:

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    //如果释放锁失败,返回false
    return false;
}

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

private void unparkSuccessor(Node node) {
    /*
     * If status is negative (i.e., possibly needing signal) try
     * to clear in anticipation of signalling.  It is OK if this
     * fails or if status is changed by waiting thread.
     */
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    /*
     * Thread to unpark is held in successor, which is normally
     * just the next node.  But if cancelled or apparently null,
     * traverse backwards from tail to find the actual
     * non-cancelled successor.
     */
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值