【Java8源码分析】locks包-AbstractQueuedSynchronizer同步器

转载请注明出处:http://blog.csdn.net/linxdcn/article/details/72844011


1 前言

目前在Java中存在两种锁机制:synchronized和Lock,Lock接口及其实现类是JDK5增加的内容。数据同步需要依赖锁,那锁的同步又依赖谁?synchronized给出的答案是在软件层面依赖JVM,而Lock给出的方案是在硬件层面依赖特殊的CPU指令。

在java中的locks包中的ReentrantLock、ReentrantReadWriteLock,还有同步包中的Semaphore、CountDownLatch类均是借助AQS同步器实现。

AQS的功能可以分为两类:独占锁和共享锁。它的所有子类中,要么实现并使用了它独占锁的API,要么使用了共享锁的API,而不会同时使用两套API,即便是它最有名的子类ReentrantReadWriteLock,也是通过两个内部类:读锁和写锁,分别实现的两套API来实现的


2 概要

AQS类中,有两件重要的事情,即获取锁和释放锁。AQS类将其分为独占模式共享模式

  • 独占模式:其他线程试图获取该锁将无法取得成功
  • 共享模式下:多个线程获取某个锁可能(但不是一定)会获得成功
// 独占模式,获取锁的入口
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
// 共享模式,释放锁的入口
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}
// 共享模式,获取锁的入口
public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}
// 共享模式,释放锁的入口
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}
// 以下四个方法需要子类实现
protected int tryAcquireShared(int arg) {
    throw new UnsupportedOperationException();
}
protected boolean tryReleaseShared(int arg) {
    throw new UnsupportedOperationException();
}
protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}
protected boolean tryRelease(int arg) {
    throw new UnsupportedOperationException();
}

上面是AQS类的几个重要方法,其中tryAcquire(int)、tryRelease(int)、tryAcquireShared(int)、tryReleaseShared(int)四个方法是需要子类实现的。


3 存储结构

在AQS中,被阻塞的线程被包装成一个Node结构,而且阻塞的线程(Node)被存储在一个双向链表当中,如下图所示

     +------+  prev +-------+  prev +------+
     | Node | <---- |  Node | <---- | Node | 
head |thread|       |thread |       |thread|  tail
     |  ws  | ----> |   ws  | ----> |  ws  | 
     +------+ next  +-------+       +------+


static final class Node {  
    // 模式,分为共享与独占  
    // 共享模式  
    static final Node SHARED = new Node();  
    // 独占模式  
    static final Node EXCLUSIVE = null;          
    // 结点状态  
    // CANCELLED,值为1,表示当前的线程被取消  
    // SIGNAL,值为-1,表示当前节点的后继节点包含的线程需要运行,也就是unpark  
    // CONDITION,值为-2,表示当前节点在等待condition,也就是在condition队列中  
    // PROPAGATE,值为-3,表示当前场景下后续的acquireShared能够得以执行  
    // 值为0,表示当前节点在sync队列中,等待着获取锁  
    static final int CANCELLED =  1;  
    static final int SIGNAL    = -1;  
    static final int CONDITION = -2;  
    static final int PROPAGATE = -3;          

    // 结点状态  
    volatile int waitStatus;          
    // 前驱结点  
    volatile Node prev;      
    // 后继结点  
    volatile Node next;          
    // 结点所对应的线程  
    volatile Thread thread;          
    // 下一个等待者  
    Node nextWaiter;  
}

4 源码分析

属性域

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

    // 头结点  
    private transient volatile Node head;      
    // 尾结点  
    private transient volatile Node tail;      
    // 状态,0代表没有线程获取该锁,>0代表线程重入的次数 
    private volatile int state;
}

AQS获取锁的比较复杂,下面以三个线程A、B为例说明

Step 1

线程A通过acquire获得锁,并且线程A执行,此时状态如图



Step 2

线程B通过acquire获得锁,过程如代码注释,中间状态如下图所示。

public final void acquire(int arg) {
    // tryAcquire返回false
    if (!tryAcquire(arg) &&
        // 先调用addWaiter
        // 再调用acquireQueued
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

private Node addWaiter(Node mode) {  
    // 新生成一个结点,默认为独占模式  
    Node node = new Node(Thread.currentThread(), mode);  
    Node pred = tail;  
    // 尾结点不为空,即已经被初始化  
    if (pred != null) { 
        node.prev = pred;   
        if (compareAndSetTail(pred, node)) {   
            pred.next = node;  
            return node;
        }  
    }  
    // 尾结点为空(即还没有被初始化过),或者是compareAndSetTail操作失败,则入队列  
    enq(node);
    return node;  
}

// 加入队列
private Node enq(final Node node) {  
    for (;;) {
        Node t = tail;  
        // 尾结点为空,即还没被初始化  
        if (t == null) {
            // 头结点为空,并设置头结点为新生成的结点 
            if (compareAndSetHead(new Node())) 
                tail = head;
        } else {  // 尾结点不为空,即已经被初始化过 
            node.prev = t;   
            if (compareAndSetTail(t, node)) {  
                t.next = node;   
                return t;
            }  
        }  
    }  
} 



Step 3

由enq函数的代码可知,其实enq是自旋的,上面展示了enq执行第一次循环时候的状态,接下来循环第二次,并返回给acquireQueued,下面来看看acquireQueued。

//判断在等待队列中的线程是否中断
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        // 自旋,每次循环尝试获得锁,如果尝试不成功后会挂起线程
        // 等待release唤起
        for (;;) {
            final Node p = node.predecessor();
            // 如果前驱节点是head节点,则尝试再获得锁,因为锁可能已经释放
            // 如果获取锁成功
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null;
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        // 前驱节点已经设置为release时通知它,故可以“停靠”在此节点后面
        return true;
    if (ws > 0) {
        // 前驱节点已经cancel,这个时候我们会除掉这个节点
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        // 如果都不是,通过CAS操作将这个前驱节点设置成SIGHNAL
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}



注意:此时head节点的waitStatus已经被更新为-1,但NodeB在逻辑上并为成功park在head节点后。

Step 4

acquireQueued是自旋的,Step 3中已经把前驱节点的状态设置为-1,即设置为通知状态,此时自旋重复上述Step 3,加入tryAcquire仍为false,即获取不了锁,此时调用shouldParkAfterFailedAcquire返回true,该NodeB在逻辑上成功park在head节点后,然后调用parkAndCheckInterrupt挂起

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
} 

Step 5

等待线程A执行完成了,调用release函数释放锁,实际上调用tryRelease,是AQS的state减1,如果返回true,则成功释放。接下来调用unparkSuccessor唤起等待线程

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

private void unparkSuccessor(Node node) {

    // 把node的状态变为0
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    // 查找下一个状态-1的节点,即需要唤起的node
    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;
    }

    // 调用unpark唤起
    if (s != null)
        LockSupport.unpark(s.thread);
}




5 总结

(1)线程被包装成Node,被保存在双向链表,同时有一个head和tail辅助指针。同时Node中还有waitStatus状态变量,只有一个Node的状态是-1时,才能停靠一个后续Node。
(2)head指针指向一个Node时,会将这个Node的状态设置为0,可以理解为当前获得锁的Node。


参考

http://blog.csdn.net/pfnie/article/details/53191892
http://www.jianshu.com/p/d8eeb31bee5c


转载请注明出处:http://blog.csdn.net/linxdcn/article/details/72844011

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值