`
agapple
  • 浏览: 1583741 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

jdk中cocurrent下的AbstractQueuedSynchronizer理解记录

    博客分类:
  • java
阅读更多

   以前虽然看过一次AQS的源码实现,但在过一段时间后与同学交流时,发觉自己理解并不够深,印像太浅。需要做一个记录整理,帮助自己消化。

 

AQS中Node的设计: 

 

几个点:

1. Node实现作者: "CLH" (Craig, Landin, and * Hagersten) ,有名的CLH queue

2. 是一个FIFO的链表的实现,对于队列的控制经常要做double-check。

3. Node节点通过一个int waiteStatus代表一些不同意义的状态。

 

  • SIGNAL=-1,代表是需要当前Node节点需要唤起后一个Node节点。在Node节点enqueue时,会设置前一个节点的状态。这样链式的唤醒,完成这样的一个交接棒。
  • CONDITION = -2 , 
4. nextWaiter一个标志位,就是用于表明是采用的共享锁还是排他锁。同时也是其对应condition队列的引用节点。

来看一下Node操作的一个double-check设计

node的equeue操作: 
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;
            if (compareAndSetTail(pred, node)) {  // 位置1
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }

private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize    // 位置3
                Node h = new Node(); // Dummy header
                h.next = node;
                node.prev = h;
                if (compareAndSetHead(h)) {   // 位置4
                    tail = node;
                    return h;
                }
            }
            else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {   // 位置2
                    t.next = node;
                    return t;
                }
            }
        }
    }
 
代码中标记了4个位置:
1.  node默认的equeue操作都是接在tail节点之后,prev节点指定完成后,进行一个cas设置操作,将当前加入的节点做为tail。 因为会有并发操作,原先的tail节点会有所变化,位置1处会出现失败。这样就进入第2步的check机制
2.  位置2,每次都取一次当前的tail节点,尝试通过cas设置操作,将当前节点做为tail,有并发变化/竞争导致处理失败,继续重复这一动作,直到完成为之。 
3.  在位置1和2处理的一种异常情况,就是tail节点为空,有可能该节点是第一个进行enqueue,也有可能是node节点被所有唤醒。这时需要重新创建队列,创建一个空的Head node节点,将自己做为tail节点。
4.  同样考虑并发因素,位置3在处理时,可能已有线程创建了Head节点,这样就又回到位置2上的处理,将node节点添加在tail节点之后。

位置2,3,4都是基于一种发展的锁机制实现,尝试N次竞争后,总能成功。

node的dequeue操作: 
一个比较巧妙的设计,需要细细品味。
出库分为两个动作: 

动作1: 
private void setHead(Node node) {
        head = node;
        node.thread = null;
        node.prev = null;
    }
轮到自己出库后,进行head节点的gc处理,断开自己node节点和链表的一些应用。同时将自己添加为head。 
说明:刚开始对出库时将自己node设置为head,一直想不明白。后来仔细看了下是否轮到出库时的判断就可以明白了。
final Node p = node.predecessor();   // 位置1
   if (p == head && tryAcquire(arg)) {
   setHead(node);
   p.next = null; // help GC
   return interrupted;
}
轮到出库时的判断:
   在位置1上,每次判断都是获取当前节点的prev节点,判断==head节点。最后就可以这么理解,head节点其实是一个“傀儡”,代表的是上一个出库的节点,因为是一个FIFO队列,如果当前的上一个节点已经出库,那就可以轮到自己

动作2: 
private void unparkSuccessor(Node node) {
        /*
         * Try to clear status in anticipation of signalling.  It is
         * OK if this fails or if status is changed by waiting thread.
         */
        compareAndSetWaitStatus(node, Node.SIGNAL, 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) { //位置1
            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);
    }
这里没啥特殊的,就是在位置1上,当一个节点需要出库后,唤醒下一个非cancel的节点。这里的LockSupport的代码设计也有一点取巧,后续可以再写点。

acquire , release , cancel三个动作设计

按照原先作者的设计: 

 

  Acquire:
      while (!tryAcquire(arg)) {
          enqueue thread if it is not already queued;
          possibly block current thread;
      }
 
  Release:
      if (tryRelease(arg))
         unblock the first queued thread;

 

 预留了5个protected方法,用于client自己实现相关的处理,进行业务行为控制,因为cocurrent很多Lock,Future的实现都是基于此扩展,定义了自己的处理。

具体的一些方法使用,后续再补。

 

acquire动作:

独占锁:

 

  1. public final void acquire(int arg)
  2. public final void acquireInterruptibly(int arg) throws InterruptedException
  3. public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException 

public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
说明:
1. tryAcquire自定义的扩展,一般用value值进行控制,类似P/V原语的控制。
2. addWaiter是一个入库的动作,前面已经介绍。
3. 来看一下accquireQueued方法
final boolean acquireQueued(final Node node, int arg) {
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();   // 位置1
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&  // 位置2
                    parkAndCheckInterrupt())  // 位置3
                    interrupted = true;
            }
        } catch (RuntimeException ex) {
            cancelAcquire(node);  // 位置4
            throw ex;
        }
    }
位置1: 前面已经介绍过,就是对比一下我的上一个节点是否已经出队列,如果已经出队列,就认为当前轮到自己出队列,返回interrupted的标志。
位置2: 执行了一个动作,就是设置一下当前节点的上一个节点的waitStatus状态为SINGLE,让其在出队列的时候能唤醒自己进行处理。
位置3: 在设置了上一个节点为SINGLE后,当前线程就可以进行park,转到阻塞状态,直到等到被唤醒。 (唤醒条件有2个: 前一个节点的唤醒和Thread.interupte事件)
位置4: 就是一个cancel动作。

和accquire方法的区别,就是针对Thread.interrupt会响应一个InterruptedException异常,立马返回。而accquire会一直等待直到自己可以出队列。

 

看一下其核心方法:
private void doAcquireInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.EXCLUSIVE);
        try {
            for (;;) {
                final Node p = node.predecessor();  // 位置1
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    return;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&  // 位置2
                    parkAndCheckInterrupt())
                    break;   // 位置3
            }
        } catch (RuntimeException ex) {
            cancelAcquire(node);
            throw ex;
        }
        // Arrive here only if interrupted
        cancelAcquire(node);
        throw new InterruptedException();   // 位置4
    }
 位置1,2和accquire一样,唯一的区别小点就在位置3上直接进行了break,跳出响应位置4了InterruptedException。

和accquire, accquireInterruptibly方法的区别,就在于在支持Interrupted响应的基础上,还支持了Timeout超时的控制,在指定时间内无法获取对应锁,抛出对应的TimeoutException
来看一下核心方法:
private boolean doAcquireNanos(int arg, long nanosTimeout)
        throws InterruptedException {
        long lastTime = System.nanoTime();
        final Node node = addWaiter(Node.EXCLUSIVE);
        try {
            for (;;) {
                final Node p = node.predecessor();   // 位置1
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    return true;
                }
                if (nanosTimeout <= 0) {   // 位置2
                    cancelAcquire(node);
                    return false;
                }
                if (nanosTimeout > spinForTimeoutThreshold &&  // 位置3
                    shouldParkAfterFailedAcquire(p, node))   // 位置4
                    LockSupport.parkNanos(this, nanosTimeout);  // 位置5
                long now = System.nanoTime();   // 位置6
                nanosTimeout -= now - lastTime;
                lastTime = now;
                if (Thread.interrupted())   // 位置7
                    break;
            }
        } catch (RuntimeException ex) {
            cancelAcquire(node);
            throw ex;
        }
        // Arrive here only if interrupted
        cancelAcquire(node);
        throw new InterruptedException();  // 位置8
    }
位置1: 和先前的accquire一样
位置2:6都是对超时时间的处理,nanosTimeout为当前还需要等待的时间,每次检查一下nanosTimeout时间,并在循环过程中减去对应的处理时间。
位置3:一个自旋锁的优化判断
位置4:和先前的accquire一样,设置上一个Node节点的waitStatus状态。
位置5:和先前accquire有点不同,使用LockSupport指定时间的park方法,完成对应的时间控制。

共享锁:

 

public final void acquireShared(int arg)

和独占锁处理方式基本类似,来看一下核心代码:

 

private void doAcquireShared(int arg) {
        final Node node = addWaiter(Node.SHARED);
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor(); 
                if (p == head) {
                    int r = tryAcquireShared(arg); 
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);   // 位置1
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } catch (RuntimeException ex) {
            cancelAcquire(node);
            throw ex;
        }
    }

这里设计上有点小技巧,原先思考一个共享锁的典型场景:读写锁。 一旦写锁释放,应该是唤起所有的读锁。而原先在看setHeadAndPropagate,并没有一个循环释放锁的过程。后来思考了下,采用的是一个链式释放的过程,前一个shared的锁对象释放下一个,在释放的时候继续进行tryAccquireShared控制。

 

一点感悟:在写并发程序时,一些传统编程的思路要有所改变。

 

public final void acquireSharedInterruptibly(int arg) throws InterruptedException

public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout) throws InterruptedException

这两个实现上和独占锁类似,也就是setHeadAndPropagate处理上的不同点而已。

 

 

release动作:

public final boolean release(int arg)

 

 

 

 

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

没啥特别好将的,一看基本也就明白了,出队列的时候,同时唤醒下一个Node。

 

cancel动作:

private void cancelAcquire(Node node) 

 

 

代码就不贴了,几个处理:

1. 从链表上删除cancel节点

2. 如果cancel节点是head,则尝试唤醒cancel节点的下一个节点。

 

 

ConditionObject的理解

几个主要方法:

 

  • public final void await() throws InterruptedException
  • public final void awaitUninterruptibly()
  • public final long awaitNanos(long nanosTimeout) throws InterruptedException
  • public final boolean awaitUntil(Date deadline) throws InterruptedException
  • public final boolean await(long time, TimeUnit unit) throws InterruptedException
  • public final void signal()
  • public final void signalAll()

先理解一下ConditionObject的应用场景,和Objectd的wait,single方法使用场景的区别。
就拿生产者/消费者程序看,假想用object.wait和single实现: 

伪代码如下:
Array queue;
Object empty = new Object();
Object full = new Object();

// 生产者
if(queue 是否满了)
   full.wait() //阻塞等待
else 
   put(queue , data) //放入数据
   empty.single(); // 已经放了一个,通知一下


// 消费者
if(queue 是否空了)
  empty.wait() // 阻塞等待
else 
  data = get(queue);
  full.single() //  已经消费了,通知一下
 
存在的问题: 
1. 如何保证put和get数据是满足是线程安全的? CAS设计 or 使用同步原语? 

很明显,CAS的设计满足不了queue的两个操作,第一数据入库,第二下标+1,存在安全隐患。所以需要使用对queue进行同步控制。但这样会引出思索的问题,拿到queue的锁后,一直在等待empty或者full通知。
这里ConditionObject就能很好的解决这个问题,不存在死的问题。它的执行一个条件的await操作时,会首先释放当前所持有的Lock,让其他的线程可以进行生产/消费处理。说白了2个object是基于同一个Node等待队列链表。

 

整体概念

在整个AQS存在两种链表。 一个链表就是整个Sync Node链表,横向链表。另一种链表就是Condition的wait Node链表,相对于Sync node,它属于node节点的一个纵向链表。当纵向列表被single通知后,会进入对应的Sync Node进行排队处理。

 

通过这样的纵横队列,实现了ConditionObject共享lock锁数据。

 

  • 大小: 67.4 KB
分享到:
评论
15 楼 yyang11 2014-05-11  
 
注释也提到了这一点
   /* We also use "next" links to implement blocking mechanics.
     * The thread id for each node is kept in its own node, so a
     * predecessor signals the next node to wake up by traversing
     * next link to determine which thread it is.  Determination of
     * successor must avoid races with newly queued nodes to set
     * the "next" fields of their predecessors.  This is solved
     * when necessary by checking backwards from the atomically
     * updated "tail" when a node's successor appears to be null.
     * (Or, said differently, the next-links are an optimization
     * so that we don't usually need a backward scan.)
     */
14 楼 yyang11 2014-05-11  
agapple 写道
khotyn 写道
问个问题,为什么唤醒继任者的时候如果next为空,要从队列的尾部开始找非cancel状态的继任者呢?为什么不是直接一路next下去呢?


先前也没特别留意,仔细看了下代码。

按照作者自己给的注释,提到的应该是cancel引起的。
/*
         * 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.
         */


unparkSuccessor方法中的代码:
Node s = node.next;
        if (s == null || s.waitStatus > 0) { //位置1
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }

注意下位置1,s.waitStatus代表的就是cancel的状态判断。
再看一下,cancelAcquire代码行:693行,node.next = node; // help GC
处理cancel节点时,会修改next节点,导致unparkSuccessor时next节点就不再是一个闭环。所以这时需要从tail一直往前查找。

还有一个原因, addWaiter方法先设置node.prev, CASTail之后再设置pred.next。CASTail是原子的,但是其他线程仍然可能看到pred.next为null的不一致的状态,这种情况下从后向前能够完成查找
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
13 楼 jnullpointer 2013-04-18  
jnullpointer 写道
agapple 写道
jnullpointer 写道
agapple 写道
jnullpointer 写道
为什么tryAcquire、tryRelease、tryReleaseShared返回的都是boolean,tryAcquireShared返回的是int?


你看它的javadoc说明阿

a negative value on failure; zero if acquisition in shared
     *         mode succeeded but no subsequent shared-mode acquire can
     *         succeed; and a positive value if acquisition in shared
     *         mode succeeded and subsequent shared-mode acquires might
     *         also succeed, in which case a subsequent waiting thread
     *         must check availability. (Support for three different
     *         return values enables this method to be used in contexts
     *         where acquires only sometimes act exclusively.)  Upon
     *         success, this object has been acquired.


有3种case的结果要标示,所以用了int


在失败时返回负值;如果在共享模式下获取成功,但后续共享模式下的获取无法成功,则返回 0;如果在共享模式下获取成功,并且后续共享模式下的获取也能成功(在这种情况下,后续等待线程必须检查可用性),则返回正值。
不太明白说明中=0和>0两种场景的含义,麻烦讲解一下。


举个case:读写锁.
1. 第1次read操作,调用tryAcquireShared,返回的是0
2. 第2次read操作,调用tryAcquireShared,返回的是1,表示还是能获取成功.
3. 第3次write操作,调用tryAcquireShared会-1,直到read操作释放锁,然后返回0
4. 第4次write操作,调用tryAcquireShared会返回-1,直到write操作释放锁之后,然后返回0


ReentrantReadWriteLock的tryAcquireShared返回值只有1和-1,没有0的返回值


有点明白了:
tryAcquireShared大部分场景只需要判断<0和>=0两种,Semaphore的tryAcquireShared就存在对于=0的情况,应该只是对调用者一个暗示,可能在某些情况下会区分=0和>0。唯一不好的地方就是这几个API变得有那么一点点不一致了。
12 楼 jnullpointer 2013-04-17  
agapple 写道
jnullpointer 写道
agapple 写道
jnullpointer 写道
为什么tryAcquire、tryRelease、tryReleaseShared返回的都是boolean,tryAcquireShared返回的是int?


你看它的javadoc说明阿

a negative value on failure; zero if acquisition in shared
     *         mode succeeded but no subsequent shared-mode acquire can
     *         succeed; and a positive value if acquisition in shared
     *         mode succeeded and subsequent shared-mode acquires might
     *         also succeed, in which case a subsequent waiting thread
     *         must check availability. (Support for three different
     *         return values enables this method to be used in contexts
     *         where acquires only sometimes act exclusively.)  Upon
     *         success, this object has been acquired.


有3种case的结果要标示,所以用了int


在失败时返回负值;如果在共享模式下获取成功,但后续共享模式下的获取无法成功,则返回 0;如果在共享模式下获取成功,并且后续共享模式下的获取也能成功(在这种情况下,后续等待线程必须检查可用性),则返回正值。
不太明白说明中=0和>0两种场景的含义,麻烦讲解一下。


举个case:读写锁.
1. 第1次read操作,调用tryAcquireShared,返回的是0
2. 第2次read操作,调用tryAcquireShared,返回的是1,表示还是能获取成功.
3. 第3次write操作,调用tryAcquireShared会-1,直到read操作释放锁,然后返回0
4. 第4次write操作,调用tryAcquireShared会返回-1,直到write操作释放锁之后,然后返回0


ReentrantReadWriteLock的tryAcquireShared返回值只有1和-1,没有0的返回值
11 楼 agapple 2013-04-16  
jnullpointer 写道
agapple 写道
jnullpointer 写道
为什么tryAcquire、tryRelease、tryReleaseShared返回的都是boolean,tryAcquireShared返回的是int?


你看它的javadoc说明阿

a negative value on failure; zero if acquisition in shared
     *         mode succeeded but no subsequent shared-mode acquire can
     *         succeed; and a positive value if acquisition in shared
     *         mode succeeded and subsequent shared-mode acquires might
     *         also succeed, in which case a subsequent waiting thread
     *         must check availability. (Support for three different
     *         return values enables this method to be used in contexts
     *         where acquires only sometimes act exclusively.)  Upon
     *         success, this object has been acquired.


有3种case的结果要标示,所以用了int


在失败时返回负值;如果在共享模式下获取成功,但后续共享模式下的获取无法成功,则返回 0;如果在共享模式下获取成功,并且后续共享模式下的获取也能成功(在这种情况下,后续等待线程必须检查可用性),则返回正值。
不太明白说明中=0和>0两种场景的含义,麻烦讲解一下。


举个case:读写锁.
1. 第1次read操作,调用tryAcquireShared,返回的是0
2. 第2次read操作,调用tryAcquireShared,返回的是1,表示还是能获取成功.
3. 第3次write操作,调用tryAcquireShared会-1,直到read操作释放锁,然后返回0
4. 第4次write操作,调用tryAcquireShared会返回-1,直到write操作释放锁之后,然后返回0
10 楼 jnullpointer 2013-04-16  
agapple 写道
jnullpointer 写道
为什么tryAcquire、tryRelease、tryReleaseShared返回的都是boolean,tryAcquireShared返回的是int?


你看它的javadoc说明阿

a negative value on failure; zero if acquisition in shared
     *         mode succeeded but no subsequent shared-mode acquire can
     *         succeed; and a positive value if acquisition in shared
     *         mode succeeded and subsequent shared-mode acquires might
     *         also succeed, in which case a subsequent waiting thread
     *         must check availability. (Support for three different
     *         return values enables this method to be used in contexts
     *         where acquires only sometimes act exclusively.)  Upon
     *         success, this object has been acquired.


有3种case的结果要标示,所以用了int


在失败时返回负值;如果在共享模式下获取成功,但后续共享模式下的获取无法成功,则返回 0;如果在共享模式下获取成功,并且后续共享模式下的获取也能成功(在这种情况下,后续等待线程必须检查可用性),则返回正值。
不太明白说明中=0和>0两种场景的含义,麻烦讲解一下。
9 楼 agapple 2013-04-16  
jnullpointer 写道
为什么tryAcquire、tryRelease、tryReleaseShared返回的都是boolean,tryAcquireShared返回的是int?


你看它的javadoc说明阿

a negative value on failure; zero if acquisition in shared
     *         mode succeeded but no subsequent shared-mode acquire can
     *         succeed; and a positive value if acquisition in shared
     *         mode succeeded and subsequent shared-mode acquires might
     *         also succeed, in which case a subsequent waiting thread
     *         must check availability. (Support for three different
     *         return values enables this method to be used in contexts
     *         where acquires only sometimes act exclusively.)  Upon
     *         success, this object has been acquired.


有3种case的结果要标示,所以用了int
8 楼 jnullpointer 2013-04-16  
为什么tryAcquire、tryRelease、tryReleaseShared返回的都是boolean,tryAcquireShared返回的是int?
7 楼 rxin2009 2012-09-15  
请教lz一个cas操作应用的问题:用cas操作处理并发的时候,如果失败,常用的补救措施(我就只知道这一种)如文中的发展锁机制实现,但是有点不明白的就是:在V的值改变使得不与expect值对应,这个时候如果他们始终不同,程序不是进了死循环了吗?
6 楼 wuwenjie0506 2011-04-10  
文中的SINGLE应为SIGNAL。
5 楼 agapple 2011-03-29  
khotyn 写道
问个问题,为什么唤醒继任者的时候如果next为空,要从队列的尾部开始找非cancel状态的继任者呢?为什么不是直接一路next下去呢?


先前也没特别留意,仔细看了下代码。

按照作者自己给的注释,提到的应该是cancel引起的。
/*
         * 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.
         */


unparkSuccessor方法中的代码:
Node s = node.next;
        if (s == null || s.waitStatus > 0) { //位置1
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }

注意下位置1,s.waitStatus代表的就是cancel的状态判断。
再看一下,cancelAcquire代码行:693行,node.next = node; // help GC
处理cancel节点时,会修改next节点,导致unparkSuccessor时next节点就不再是一个闭环。所以这时需要从tail一直往前查找。
4 楼 khotyn 2011-03-29  
问个问题,为什么唤醒继任者的时候如果next为空,要从队列的尾部开始找非cancel状态的继任者呢?为什么不是直接一路next下去呢?
3 楼 agapple 2011-03-22  
nuse3023 写道
final boolean acquireQueued(final Node node, int arg) 方法中的
这段:
 if (shouldParkAfterFailedAcquire(p, node) &&   
                    parkAndCheckInterrupt())   
                    interrupted = true;   


我自己理解的是如果前一个判断shouldParkAfterFailedAcquire返回true,则说明node线程可以放心的去park,因为它的前一个节点的waitStatus已经为Signal了,所以进入了下一个判断parkAndCheckInterrupt,看了下:
    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }


其中LockSupport.park(this);是把当前线程阻塞住,这个是不是意味着当先线程执行被阻塞,也就暂时停止了在acquireQueued方法中的for(;;)的自旋循环呢?
如果轮到改node出queue时,它被唤醒,自旋循环又开始了呢?

不清楚理解是不是有误,望楼主赐教啊~  


1. 自旋锁的理解有偏差。 一般自旋锁是以cpu换效率。如果thread.wait阻塞100ns超时,线程切换(阻塞/唤醒)的代价比较高,肯能超过了100ns。这时就可以选择while(true){}的死循环来处理。
Exchanger类中有一个自旋锁的使用:
private static Object spinWait(Node node, Slot slot) {
        int spins = SPINS;
        for (;;) {
            Object v = node.get();
            if (v != null)
                return v;
            else if (spins > 0)
                --spins;
            else
                tryCancel(node, slot);
        }
    }


2. 其中LockSupport.park(this);是把当前线程阻塞住。这意味着当前的线程处于WAITING状态,Node队列的上一个节点会唤醒该节点。重新回到for(;;)进行检查,这里不是一个自旋锁。可以理解为一个乐观锁的实现。

比如我们在数据库上常用的乐观锁: update xxxx set A = A+1 where A = xxx。 每次都先check一下当前的current value,然后再进行相应的操作。如果更新失败,继续for(;;),直到更新成功。
2 楼 nuse3023 2011-03-21  
final boolean acquireQueued(final Node node, int arg) 方法中的
这段:
 if (shouldParkAfterFailedAcquire(p, node) &&   
                    parkAndCheckInterrupt())   
                    interrupted = true;   


我自己理解的是如果前一个判断shouldParkAfterFailedAcquire返回true,则说明node线程可以放心的去park,因为它的前一个节点的waitStatus已经为Signal了,所以进入了下一个判断parkAndCheckInterrupt,看了下:
    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }


其中LockSupport.park(this);是把当前线程阻塞住,这个是不是意味着当先线程执行被阻塞,也就暂时停止了在acquireQueued方法中的for(;;)的自旋循环呢?
如果轮到改node出queue时,它被唤醒,自旋循环又开始了呢?

不清楚理解是不是有误,望楼主赐教啊~  
1 楼 nuse3023 2011-03-21  
很给力啊~   回去好好研读下 这里自己在理解上有不少问题

相关推荐

Global site tag (gtag.js) - Google Analytics