j.u.c.locks.AbstractQueuedSynchronizer.Node

原创
2014/06/22 22:56
阅读数 834

AQS是JUC当中最核心的部分,大部分多线程讲解,都不会详细讲AQS,AQS的源代码,要看明白还是有点困难的。但是一旦看明白了,结构还是蛮清晰的。这里我们把AQS拆开,分成几部分来讲,就会很清楚了。首先先从内部类Nde讲起

上源代码:

static final class Node {
        static final int CANCELLED =  1;
        static final int SIGNAL    = -1;
        static final int CONDITION = -2;
        static final Node SHARED = new Node();
        static final Node EXCLUSIVE = null;

        volatile int waitStatus;

        volatile Node prev;

        volatile Node next;

        volatile Thread thread;

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

这个NODE类是java.util.concurrent.locks.AbstractQueuedSynchronizer的内部类。主要实现了一个链表的数据结构中的节点。先不谈这个链表是做什么的,我们先看看这个节点是怎么构造的。

首先,这个节点有四个状态 ,其中三在Node类中已经明确定义,另外一个状态为初始状态,值为0

        static final int CANCELLED =  1;
        static final int SIGNAL    = -1;
        static final int CONDITION = -2;

1 :当前节点被取消或者中断

-1:下一个节点需要被释放

-2:当前节点正处在等待队列中

0 :不属于任何一种


其次,这个节点有两种模式,其中SHARED是共享模式,EXCLUSIVE是独占模式。

        static final Node SHARED = new Node();
        static final Node EXCLUSIVE = null;

等一下再介绍这两个模式。先把数据结构看完。


然后是成员变量:

        volatile int waitStatus;

        volatile Node prev;

        volatile Node next;

        volatile Thread thread;

        Node nextWaiter;

volatile int waitStatus:当前节点的状态,一共四种

volatile Node prev:链表的前一个节点

volatile Node next:链表的后一个节点

volatile Thread thread:这个节点所处的线程

Node nextWaiter:这个节点等待的模式(共享模式和独占模式)


接下去的两个方法和三个构造方法非常简单,就不讲了。


问题1:这个链表是如何工作的?

用一张图表示队列的工作方式:

    1. 每个节点都拥有一个指针,知道自己的前一个节点

    2. 每个节点都有一个状态位,表示是否占用资源

    3. 每个节点都要完成自旋判断上一个节点的状态(图中while循环),才能真正的执行自己的逻辑

    4. 当逻辑执行完,会修改自己的节点的状态,放开下个节点的自旋

    5. 新插入的节点,永远在队尾


上图已经非常清楚的描述了这个队列的工作方式。这就是CLH锁的原理。其实实现锁还有几种不同的方式。(点击了解更多锁的实现原理和比较


由于一个线程的状态不止两种,所以Node的状态实现,并没有用True/False表示。而是用了waitStatus表示。另外为了操作的方便,Node并没有使用单向链表,而是双向链表。这样操作起来会方便很多。

如下图:

  1. 每个节点都拥有一个指针,知道自己的前一个节点和后一个节点

  2. 每个节点都有一个状态位,表示是否占用资源

  3. 每个节点都要完成自旋判断上一个节点的状态(图中while循环),才能真正的执行自己的逻辑

  4. 当逻辑执行完,会修改自己的节点的状态,放开下个节点的自旋

  5. 新插入的节点,永远在队尾。队尾节点标记新插入节点

  6. 被取消或者中断的线程节点,会从链表中断开,被删除

还记得在讲LockSupport(点击查看源码)中是如何挂起和恢复一个线程的吗?Thread对象就是做这个的。

到此为止,我们的图与Node类的结构是不是已经很像了呢?


问题2:共享模式和独占模式

共享模式和独占模式是两种不同的锁定资源的方式。

最经典的举例就是对共享文件的操作。当第一个,第二个用户都请求读取文件内容时,并不会阻塞用户的读取行为,并且可以并发的读取。

但是当第三个用户请求修改的时候,就会获取一个独占锁。一旦获取了独占锁,所有共享锁的获取或者独占锁的获取都会被阻塞,一直等

到独占锁被释放才会解除阻塞。

实际在JUC中读写锁也是一个很典型的共享与独占的例子。















展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
1 收藏
0
分享
返回顶部
顶部