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

来源:互联网 发布:centos 创建文件 编辑:程序博客网 时间:2024/05/24 07:05

转载请注明出处: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 0
原创粉丝点击