`
agapple
  • 浏览: 1595448 次
  • 性别: 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  
很给力啊~   回去好好研读下 这里自己在理解上有不少问题

相关推荐

    jdk 6u11 AbstractQueuedSynchronizer.class

    这是 jdk 6u11 的jre 解压以后得到的 rt.jar, 将rt.jar 解压获得了 AbstractQueuedSynchronizer.class 文件

    jdk 6u11 AbstractQueuedSynchronizer.Node.class

    这是 jdk 6u11 的jre 解压以后得到的 rt.jar, 将rt.jar 解压获得了 AbstractQueuedSynchronizer.Node.class 文件

    JDK1.8中文文档 JDK1.8中文 jkd8中文文档 JDK中文版

    默认方法允许在接口中定义具有实现的方法,这样可以在不破坏已有实现的情况下为接口添加新的功能。这在升级API时尤其有用,避免了因增加新方法而导致的类不兼容问题。 另外,Java 8还引入了新的日期和时间API,`...

    jdk17中文说明文档

    "方便已义中文方式浏览jdk中的说明"强调了这个文档是中文翻译版,使得开发者能够更轻松地理解JDK中的各种类、接口、方法和概念,降低了学习和使用的难度。 **标签解析:** "范文/模板/素材" 这个标签可能是指这个...

    Jdk11中文帮助文档

    在学习过程中,结合JDK 11 API中文帮助文档,开发者可以深入理解Java 11的新特性和API,从而更好地利用这些功能来编写高效、稳定的Spring应用。同时,持续关注Oracle官方更新,了解新版本的改进,将有助于保持技术的...

    jdk8中文说明文档_CHM.zip jdk1.8文档 jdk1.8说明文档

    9. **并发改进**:JDK 8对`ForkJoinPool`和`CompletableFuture`进行了优化,提供了一种更有效的并行计算方式,有助于提升多核环境下的程序性能。 10. **类型推断增强**:编译器现在能更好地推断局部变量的类型,...

    JDK中文文档

    这个"JDK中文文档"是官方文档的汉化版本,旨在为Java开发者提供方便快捷的查询途径,帮助他们更好地理解和运用Java的各种特性和功能。 **1. Java基础** Java是一种面向对象的编程语言,其设计目标是具有可移植性、...

    JDK8 中文帮助文档(jdk api 1.8 google.CHM)

    JDK8对并发库也进行了增强,如`ConcurrentHashMap`的改进,新增`AtomicIntegerArray`、`LongAdder`等原子类型,以及`ForkJoinPool`和`Parallel Streams`的引入,提高了多线程环境下的性能。 **9. Nashorn ...

    JDK 1.8中文API文档

    JDK 1.8是Java发展历程中的一个重要版本,引入了许多新特性和改进,使得开发者能够更高效地编写代码。 1. **Lambda表达式** JDK 1.8引入了Lambda表达式,这是一种简化函数式编程的方式。它允许我们将函数作为参数...

    jdk8中文API文档

    下面,我们将深入探讨JDK 8中的关键知识点。 1. **Lambda表达式**: Lambda表达式是JDK 8最具代表性的新特性,它简化了函数式编程,允许开发者用更简洁的方式处理匿名函数。例如,你可以使用lambda表达式来定义...

    jdk_8中文文档

    本中文文档旨在为Java程序员提供详尽的参考和指导,帮助他们更好地理解和利用JDK 8的新功能。 **主要特性** 1. **lambda表达式**:JDK 8引入了lambda表达式,使得编写函数式编程风格的代码变得更加简洁。Lambda...

    dubbo-admin在jdk1.8环境下运行

    dubbo-admin在jdk1.8环境下运行,dubbo-admin在jdk1.8环境下运行dubbo-admin在jdk1.8环境下运行dubbo-admin在jdk1.8环境下运行dubbo-admin在jdk1.8环境下运行dubbo-admin在jdk1.8环境下运行dubbo-admin在jdk1.8环境下...

    jdk1.8 源码中文版,jdk直接显示中文注释

    下载后直接去本机jdk目录里替换jdk中的src.zip 再打开idea就能看到中文版的源码注释 示例 https://blog.csdn.net/a7459/article/details/106495622

    jdk8帮助文档 jdk8帮助文档jdk8帮助文档

    jdk8帮助文档jdk8帮助文档jdk8帮助文档jdk8帮助文档jdk8帮助文档jdk8帮助文档jdk8帮助文档jdk8帮助文档jdk8帮助文档jdk8帮助文档jdk8帮助文档jdk8帮助文档jdk8帮助文档jdk8帮助文档jdk8帮助文档jdk8帮助文档jdk8帮助...

    jdk-11中文api修订版

    “jdk-11中文api修订版”文档详细记录了这些变化以及JDK 11中所有的类和接口,包括它们的构造函数、方法、字段等。CHM和CHW格式都是常见的帮助文档格式,方便用户离线查阅。CHM(Compiled HTML Help)是一种微软的...

    jdk9中文API文档

    Stream API 在 JDK 9 中得到了扩展,新增了 `takeWhile()` 和 `dropWhile()` 方法,它们可以根据条件过滤流中的元素。 **6. 链接服务(Link Service)** JDK 9 提供了一个链接服务,允许模块在运行时动态发现和使用...

    JDK8 API 中文 文档.CHM

    **JDK8 API中文文档**是Java开发人员的重要参考资料,它包含了Java Development Kit(JDK)8版本的所有公共类、接口、枚举和...通过查阅JDK8 API中文文档,开发者可以深入理解这些特性的使用,提升编程效率和代码质量。

    jdk1.5环境下编译的jar包在JDK1.4环境下运行

    这样,原本在JDK1.5环境下编译的jar包就可以在JDK1.4环境中正常运行了。 使用Retrotranslator的步骤如下: 1. 下载Retrotranslator,例如这里使用的是Retrotranslator-1.2.9版本。 2. 设置好JDK环境变量,确保...

    JDK1.8手册,中文

    这份中文版的JDK 1.8手册对于学习和理解这个版本的Java技术至关重要。 一、Lambda表达式 JDK 1.8引入了Lambda表达式,这是一种简洁的函数式编程语法,可以用于表示无状态、无副作用的函数。Lambda表达式允许将匿名...

    jdk 1.8 中文api文档

    **JDK 1.8 中文API文档**是Java开发者的重要参考资料,它包含了JDK 1.8版本的所有核心类库、接口、方法和异常的详细说明,方便开发者理解和使用。这个文档是基于谷歌翻译的版本,虽然可能存在部分翻译不准确的情况,...

Global site tag (gtag.js) - Google Analytics