jdk1.8 J.U.C并发源码阅读------AQS之独占锁的获取与释放

来源:互联网 发布:淘宝木子禅佛珠怎么样 编辑:程序博客网 时间:2024/05/16 18:40

一、继承关系

since1.5public abstract class AbstractQueuedSynchronizer    extends AbstractOwnableSynchronizer    implements java.io.Serializable 
继承自AbstractQueuedSynchronizer抽象类。

二、成员变量

private transient volatile Node head;//CLH队列首节点private transient volatile Node tail;//CLH队列尾节点private volatile int state;//锁的占用次数

三、内部类

CLH队列(链表结构的队列)的node的数据结构:

static final class Node {       //标志Node的状态:独占状态        static final Node SHARED = new Node();        //共享状态        static final Node EXCLUSIVE = null;        //因为超时或者中断,node会被设置成取消状态,被取消的节点时不会参与到竞争中的,会一直保持取消状态不会转变为其他状态;        static final int CANCELLED =  1;       //该节点的后继节点被阻塞,当前节点释放锁或者取消的时候(cancelAcquire)需要唤醒后继者。        static final int SIGNAL    = -1;       //CONDITION队列中的状态,CLH队列中节点没有该状态,当将一个node从CONDITION队列中transfer到CLH队列中时,状态由CONDITION转换成0        static final int CONDITION = -2;        //该状态表示下一次节点如果是Shared的,则无条件获取锁。        static final int PROPAGATE = -3;         //当一个新的node在CLH队列中被创建时初始化为0,在CONDITION队列中创建时被初始化为CONDITION状态        volatile int waitStatus;        //队列中的前驱节点        volatile Node prev;         //next连接指向后继节点,当前节点释放锁时,需要唤醒它后面第一个非cancelled状态的节点。 //当一个节点入队时,直接添加到队尾,在acquireQueue中调用shouldParkAfterFailedAcqurie时修改前一个节点的状态,若前一个节点是cancelled则直接删除前驱节点,调整prev指向非cancelled的前驱;若前驱节点的状态是0,-3时,调整状态为signal //在enq方法中,只有当成功的将tail指针指向新的尾节点时,才给之前的尾节点设置next的值,因此,当一个节点的next指针是null时,并不意味着该节点是尾节点。 //cancelled状态的node的next指向该节点自身而不是null。        volatile Node next;        //当前线程        volatile Thread thread;//CONDITION队列中指向下一个node的指针,CLH队列中不使用        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;        }    }


四、方法说明

获取锁有三种方式:
(1)acquire:以独占的模式获取一个锁,忽略中断,获取成功返回true,否则将该线程加入等待队列
public final void acquire(int arg) {//tryAcquire尝试获得该arg,失败则先addWaiter(Node.EXCLUSIVE)加入等待队列,然后acquireQueued。        if (!tryAcquire(arg) &&            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//中断当前线程            selfInterrupt();    }//tryAcquire(arg):尝试获得该arg//模板方法:由子类自己实现 protected boolean tryAcquire(int arg) {        throw new UnsupportedOperationException();    }//mode:设置该节点是共享的还是独占的, Node.EXCLUSIVE for exclusive, Node.SHARED for sharedprivate Node addWaiter(Node mode) {        Node node = new Node(Thread.currentThread(), mode);        //pred指向尾节点        Node pred = tail;//尾节点不为null        if (pred != null) {//新node的前驱结点指向尾节点            node.prev = pred;//compareAndSetTail(pred, node):若等待队列的尾节点指向pred,则将尾节点指针指向node            if (compareAndSetTail(pred, node)) {//修改AQS的尾节点成功,则将原先尾节点的next指向新节点                pred.next = node;//返回当前节点                return node;            }        }//pred==null或者修改AQS尾节点失败,则进入enq,用一个for循环修改AQS的尾指针,直至成功        enq(node);        return node;    }private Node enq(final Node node) {//循环,原因:可能同时存在多个线程修改tail指针。        for (;;) {//t指向队列的tail            Node t = tail;            if (t == null) { // Must initialize//尾节点为null,则队列为空,需要初始化队列                if (compareAndSetHead(new Node()))                    tail = head;            } else {//将node的prev指针指向AQS的tail                node.prev = t;//如果队列的tail==t,则将tail指向node                if (compareAndSetTail(t, node)) {//原先的尾节点的next指向新添加的node                    t.next = node;//修改成功,返回原先的尾节点,失败则进行下一次循环                    return t;                }            }        }    }//compareAndSetHead和compareAndSetTail都是调用unsafe对象的compareAndSwapObject实现的。/**     * CAS head field. Used only by enq.     */    private final boolean compareAndSetHead(Node update) {        return unsafe.compareAndSwapObject(this, headOffset, null, update);    } /**     * CAS tail field. Used only by enq.     */    private final boolean compareAndSetTail(Node expect, Node update) {        return unsafe.compareAndSwapObject(this, tailOffset, expect, update);    }//acquireQueued:忽略中断final boolean acquireQueued(final Node node, int arg) {        boolean failed = true;        try {            boolean interrupted = false;            for (;;) {//获取node的前驱节点p                final Node p = node.predecessor();//前驱p是首节点,则当前线程尝试获取锁                if (p == head && tryAcquire(arg)) {//获取成功,则将当前节点设置成首节点                    setHead(node);//删除前驱的next引用                    p.next = null; // help GC                    failed = false;//返回中断标志                    return interrupted;                }//node的前驱节点p不是首节点head,则通过shouldParkAfterFailedAcquire来判断当前node的线程是否应该挂起park                if (shouldParkAfterFailedAcquire(p, node) &&//应该park,则调用parkAndCheckInterrupt忽略中断进行挂起                    parkAndCheckInterrupt())                    interrupted = true;//当该线程被唤醒时,尝试再次判断前驱节点p是否为head,如果是则再次尝试获取锁,这样做的目的是为了维持FIFO的顺序//采用阻塞park和唤醒unpark的方式进行自旋式检测获取锁            }        } finally {            if (failed)                cancelAcquire(node);        }    }private void setHead(Node node) {        head = node;//thread已经获得了锁,取消Node中指向thread的引用        node.thread = null;//前驱设置为null        node.prev = null;    }//shouldParkAfterFailedAcquire:当前节点获取锁失败时,是否应该进入阻塞状态//(1)前驱节点的waitStatus==SINGNAL(-1):则说明该节点需要继续等待直至被前驱节点唤醒,返回true.//(2)前驱节点的waitStatus==cancelled(1):则说明前驱节点曾经发生过中断或者超时,则前驱节点是一个无效节点,继续向前寻找一个node,该node的waitStatus<0,同时将waitStatus>0(cancelled)的节点删除,返回false(重新检测)。若新的前驱节点是head且tryAcquire获取成功,则该线程成功获取到锁。//(3)前驱节点的waitStatus==-3或0(PROPAGATE):共享锁向后传播,因此需要将前驱节点的waitStatus修改成signal,然后返回false,重新检测前驱节点;0,表示前驱节点是队列尾节点,该节点新添加进来,修改为signal表示原先尾节点后面还有需要唤醒的节点(该节点)。private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {//ws:前驱节点的状态        int ws = pred.waitStatus;//如果前驱节点是SIGNAL状态,则返回true,表示后继节点node应该被阻塞park        if (ws == Node.SIGNAL)            return true;//ws>0:cancelled,前驱节点是一个超时或者中断被取消的Node        if (ws > 0) {           //循环往前找第一个不是cancelled状态的节点,同时删除中间状态是cancelled的节点,调整队列。            do {                node.prev = pred = pred.prev;            } while (pred.waitStatus > 0);            pred.next = node;        } else {          //状态是-3PROPAGATE或0(CONDITION状态只能在CONDITION队列中出现,不会出现在CLH队列中),则将pred前驱节点的状态设置成SIGNAL状态,表示pred节点被唤醒时,需要park它的后继者。            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);        }        return false;    }//parkAndCheckInterrupt:阻塞当前线程,并且返回中断状态private final boolean parkAndCheckInterrupt() {        LockSupport.park(this);        return Thread.interrupted();    }//当acquireQueued的try中抛出异常,并且node没有成功获取锁时,需要通过cancelAcquire方法取消node节点//主要功能://(1)重新设置node的prev指针(相当于删除了node的前面连续几个cancelled节点)//(2)将node的状态设置为cancelled//(3)如果node的prev不是首节点,并且node的next节点不是一个cancelled节点,则设置node的prev指向node的next节点;否则,node的prev节点head节点,则需要唤醒node的后继节点(unparkSuccessor)//(4)将node的next指针指向自己private void cancelAcquire(Node node) {        // Ignore if node doesn't exist        if (node == null)            return;        node.thread = null;        //如果node的前驱节点也是一个cancelled的节点,则修改node的prev指针,直至指向一个不是cancelled状态的node//相当于删除了node到prev之前的状态为calcelled的前驱节点//原因:若新的前驱节点是head节点,则需要在该node失效时唤醒node的后继节点        Node pred = node.prev;        while (pred.waitStatus > 0)            node.prev = pred = pred.prev;        //predNext是一个cancelled的节点(可能是node)。        Node predNext = pred.next;       //直接将node的状态设置为cancelled        node.waitStatus = Node.CANCELLED;        //如果是尾节点,直接删除node        if (node == tail && compareAndSetTail(node, pred)) {            compareAndSetNext(pred, predNext, null);        } else {            // 当pred不是首节点时,如果node的后继节点需要signal,则尝试将pred的状态设置成signal,并且将pred的next指针指向node的next节点            int ws;            if (pred != head &&                ((ws = pred.waitStatus) == Node.SIGNAL ||                 (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&                pred.thread != null) {                Node next = node.next;                if (next != null && next.waitStatus <= 0)                    compareAndSetNext(pred, predNext, next);            } else {//当pred是首节点时,需要唤醒node的后继节点。                unparkSuccessor(node);            }//将cancelled的节点的next指针指向node自身            node.next = node; // help GC        }    }//唤醒node的第一个非cancelled状态的node节点private void unparkSuccessor(Node node) {                int ws = node.waitStatus;//如果该节点是一个cancelled的节点,则不用管它;否则,将该节点状态置0清空,表示该节点已经获取过锁了,要出队。        if (ws < 0)            compareAndSetWaitStatus(node, ws, 0);        //node的后继节点有可能是一个cancelled的节点,而cancelled节点的next指针指向其自身。//只能从tail开始寻找到node之间最靠近node的那一个非cancelled的节点,并唤醒它。//此处不删除node到s之间的cancelled节点,只负责唤醒s节点,删除操作在shouldParkAfterFailedAquired中进行        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);    }
总结:
         首先调用tryAcquire尝试获取锁,获取失败则调用addWaiter,将该节点以独占的状态入队,加入到队列的末尾,由于可能存在多个线程调用CAS操作将节点加入到CLH队列末尾,因此enq方法以for循环的方式进行,保证该节点能添加到队列末尾。
然后调用acquireQueued在队列中获取锁,过程为:检查该节点node的前驱节点是否为head节点
如果是head节点并且调用tryAcquire获取锁成功,则setHead,将node作为当前队列的队首,原先的首节点出队。
否则,调用shouldParkAfterFailedAcquire查看当前节点的线程是否应该park,具体为:如果当前结点node的前一个节点的waitStatus为signal则表示node节点应该park等待前一个节点唤醒它,返回true;如果当前节点的小于0,即为cancelled,则应该删除node前面连续的cancelled节点,将node的prev指针指向靠近node的第一个非cancelled节点,返回false,重新判断前驱状态;若为0或者为PROPAGATE,则CAS将前驱状态改成signal返回false,重新判断。
如果应该park,则调用parkAndCheckInterrupt将当前线程park,否则就进行下次循环。
如果以上过程中抛出异常并且该节点还没有成功获取锁,则指向cancelAcquire,将当前节点的状态改为cancelled,该节点不在参与获取锁,同时删除该节点前面连续的cancelled状态的节点,若删除之后发现该节点的前一个节点是head,则应该调用unparkSuccessor唤醒后继。

(2)acquireInterruptibly:以独占的方式获取一个锁,如果该线程发生中断则抛出异常,将该node的waitstate设置为0,放弃等待锁。
public final void acquireInterruptibly(int arg)            throws InterruptedException {//检查当前线程是否被中断,中断则抛出异常        if (Thread.interrupted())            throw new InterruptedException();//尝试获取锁,获取失败则调用doAcquireInterruptibly        if (!tryAcquire(arg))            doAcquireInterruptibly(arg);    }//doAcquireInterruptibly:获取锁,中断则抛出异常,修改node的waitstate=0private void doAcquireInterruptibly(int arg)        throws InterruptedException {//将该线程以独占的方式加入锁等待队列        final Node node = addWaiter(Node.EXCLUSIVE);        boolean failed = true;        try {//自旋式获取锁,可能经过多个park和unpark的过程            for (;;) {//p指向node的前驱节点                final Node p = node.predecessor();//如果前驱节点p是head,则再次尝试获得锁                if (p == head && tryAcquire(arg)) {//获取锁成功,修改head指针,指向当前节点。//这里不用像setTail一样,因为等待队列是按照FIFO的顺序来获取锁的,因此不可能存在多个线程同时操作head,而tail可能同时存在多个node要添加到队列尾部,并发则需要循环添加至tail,直至成功。                    setHead(node);//原先的头结点出队,将内容设置为null,方便回收                    p.next = null; // help GC                    failed = false;                    return;                }//否则,前驱节点p不是head或者获取锁失败,则调用shouldParkAfterFailedAcquire查询该node是否应该阻塞park                if (shouldParkAfterFailedAcquire(p, node) &&//应该阻塞,则调用parkAndCheckInterrupt进行中断式阻塞                    parkAndCheckInterrupt())//如果在阻塞时该线程发生异常,则在此处抛出异常。                    throw new InterruptedException();            }//如果抛出异常,则在finally中cancelAcquire(将node的waitstate修改)        } finally {            if (failed)                cancelAcquire(node);        }    }private final boolean parkAndCheckInterrupt() {//阻塞当前线程        LockSupport.park(this);//返回中断状态。        return Thread.interrupted();    }//cancelAcquire:取消当前线程等待锁的状态private void cancelAcquire(Node node) {        // Ignore if node doesn't exist        if (node == null)            return;        node.thread = null;        // Skip cancelled predecessors        Node pred = node.prev;//找到前驱节点不是cancelled的,将node的prev指向它//目的是为了判断新前驱如果是head,则该节点失效需要唤醒后继节点        while (pred.waitStatus > 0)            node.prev = pred = pred.prev;        //prev节点的后继节点,可能不是node,因为while循环中只是修改了prev指向,没有更改next指向        Node predNext = pred.next;        //当前节点的waiteStatus设置为CANCELLED,表示发生中断或者超时        node.waitStatus = Node.CANCELLED;//如果node是尾节点,则直接删除并重新设置tail指向。        if (node == tail && compareAndSetTail(node, pred)) {            compareAndSetNext(pred, predNext, null);        } else {                        int ws;//前驱结点pred不是head,将pred的waitStatus设置成SIGNAL,并将pred的next指针指向node的下一个节点            if (pred != head &&                ((ws = pred.waitStatus) == Node.SIGNAL ||                 (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&                pred.thread != null) {                Node next = node.next;                if (next != null && next.waitStatus <= 0)                    compareAndSetNext(pred, predNext, next);            } else {//尝试唤醒node的后继节点                unparkSuccessor(node);            }            node.next = node; // help GC        }    }private void unparkSuccessor(Node node) {        //获取当前node的waitSatus        int ws = node.waitStatus;//<0,该节点等待锁        if (ws < 0)//重置该node的状态为0            compareAndSetWaitStatus(node, ws, 0);//s指向node的后继节点        Node s = node.next;//如果没有后继node或者后继node是canceld,因为超时或者中断被取消        if (s == null || s.waitStatus > 0) {            s = null;//从尾节点开始,寻找从s到tail之间最靠近s的waitStatus<0的nodek,将s指向它            for (Node t = tail; t != null && t != node; t = t.prev)                if (t.waitStatus <= 0)                    s = t;        }//s不为空,则有可以操作的后继节点        if (s != null)//唤醒该节点后继节点的线程。//该头结点的线程已经获得过锁并且执行完成了,还唤醒了后继节点,要将该节点从队列中移除(具体操作在该节点后继节点acquiredQueue的方法中实现更新表头)            LockSupport.unpark(s.thread);    }

(3)tryAcquireNanos:尝试以独占的方式获取锁,如果发生中断和超时则停止
 public final boolean tryAcquireNanos(int arg, long nanosTimeout)            throws InterruptedException {//发生中断,停止        if (Thread.interrupted())            throw new InterruptedException();//尝试获取独占锁,失败则调用doAcquireNanos        return tryAcquire(arg) ||            doAcquireNanos(arg, nanosTimeout);    }private boolean doAcquireNanos(int arg, long nanosTimeout)            throws InterruptedException {        if (nanosTimeout <= 0L)            return false;//截止时间        final long deadline = System.nanoTime() + nanosTimeout;//创建一个Node,独占模式        final Node node = addWaiter(Node.EXCLUSIVE);        boolean failed = true;        try {            for (;;) {//前驱节点                final Node p = node.predecessor();//如果p是head,并且tryAcquire获取成功,则该node获取锁成功                if (p == head && tryAcquire(arg)) {//设置新node为head节点,node之前的节点都出队了                    setHead(node);                    p.next = null; // help GC                    failed = false;                    return true;                }//还剩多少时间                nanosTimeout = deadline - System.nanoTime();//超时                if (nanosTimeout <= 0L)                    return false;//shouldParkAfterFailedAcquire,查看是否需要阻塞该线程,如果需要则                if (shouldParkAfterFailedAcquire(p, node) &&//查看剩余时间大于spinForTimeoutThreshold,如果大于则阻塞当前线程,否则,剩余时间很短,直接查看是否能成功获取锁(为了超时时间的准确性)                    nanosTimeout > spinForTimeoutThreshold)                    LockSupport.parkNanos(this, nanosTimeout);//如果该线程发生了中断,则抛出异常                if (Thread.interrupted())                    throw new InterruptedException();            }        } finally {//如果获取失败,则cancelAcquire,该节点要么超时,要么抛出异常了            if (failed)                cancelAcquire(node);        }    }

释放独占锁的过程分析:
总结:
调用tryRelease释放锁,若成功了,则查看head的状态,若waitStatus为0,则表示CLH队列中没有后继节点了,则不用唤醒,否则,需要调用unparkSuccessor唤醒后继节点,unparkSuccessor唤醒后继节点的原理是:找到node的后面第一个非cancelled状态的节点进行唤醒。
public final boolean release(int arg) {//tryRelease,模板方法,子类实现。尝试释放锁,释放成功,则需要唤醒后继节点        if (tryRelease(arg)) {            Node h = head;//h.waitStatus为0说明CLH队列中已经没有成员了,head是最后一个节点            if (h != null && h.waitStatus != 0)//唤醒后继节点                unparkSuccessor(h);            return true;        }        return false;    }private void unparkSuccessor(Node node) {               int ws = node.waitStatus;//waitStatus<0,重置node的状态为0,该节点已经获取过锁了。        if (ws < 0)            compareAndSetWaitStatus(node, ws, 0);        Node s = node.next;//s节点的状态是cancelled,需要寻找s后面第一个非cancelled状态的节点唤醒//从队尾开始向前找的原因://next节点不一定指向它的后继节点,有可能指向其自身,当该节点的状态为cancelled的时候,该节点的next指针指向自身。//之所以cancelled状态节点的next指针指向自身,是为了方便该方法isOnSyncQueue(Node node)判断,若next不为空则说明该Node一定在CLH队列中。        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)//唤醒线程,后继线程在AcquireQueue中被唤醒。//删除node到s之间的cancelled状态的线程的工作,在shouldParkAfterFailedAcquire中进行,当前驱状态为cancelled时,//寻找第一个非cancelled状态的节点,调整s的prev指向,如果前驱是head,并且tryAcquire成功,则s成功获取到锁了。            LockSupport.unpark(s.thread);    }





阅读全文
1 0