并发 - Java的AQS.Node源码疑惑
大家讲道理
大家讲道理 2017-04-17 18:00:30
0
2
872

AbstractQueuedSynchronizerNode内部类中,对volatile Node prev成员变量获取方法predecessor()如下

   
    final Node predecessor() throws NullPointerException {
        Node p = prev;
        if (p == null)
            throw new NullPointerException();
        else
            return p;
    }

在源码中,这里对volatile类型的成员变量prev的返回,是先把他赋值给一个中间变量p,然后拿p返回。
这种设计在AQS的源码中很多地方都有涉及到,包括在其它源码中也经常看到对volatile类型的变量先赋值给另外一个变量,然后把这个变量返回.
这样设计的目的是什么?

大家讲道理
大家讲道理

光阴似箭催人老,日月如移越少年。

reply all(2)
迷茫
// Works with acquire/release semantics for volatile in Java 1.5 and later 
// Broken under Java 1.4 and earlier semantics for volatile 
class Foo {
    private volatile Helper helper;
    public Helper getHelper() {
        Helper result = helper;
        if (result == null) {
            synchronized(this) {
                result = helper;
                if (result == null) {
                    helper = result = new Helper();
                }
            }
        }
        return result;
    }
// other functions and members... }

Note the local variable result, which seems unnecessary. The effect of this is that in cases where helper is already initialized (i.e., most of the time), the volatile field is only accessed once (due to "return result;" instead of "return helper;"), which can improve the method's overall performance by as much as 25 percent.[6]

If the helper object is static (one per class loader), an alternative is the initialization on demand holder idiom[7] (See Listing 16.6[8] from the previously cited text.)

-------Wikipedia

伊谢尔伦

The effect of

is less obvious in predecessor()这个方法里,Node p. Please allow me to make the example a little more extreme:

final Node extremePredecessor() throws NullPointerException {
    // #L0: Node p = prev;
    // #L1
    if (crazyConditional_1(prev))  {
      ...
    }
    // #L2
    else if (crazyConditional_2(prev))  {
      ...
    }        
    // #L3
    else if (crazyConditional_3(prev)) {
      ...
    }
    // #L4
    else {
      return prev;
    }
}

Assuming that 100 threads calls will change the value of prev, then between #L1 and #L4, any changes to the shared variable -- prev will be visible to extremePredecessor().
This will have the following problems:

  • is very similar to synchronization lock. Synchronous update of prev will cause performance loss, and prev will become a bottleneck for the entire Queue.

  • The value of prev between #L1 and #L4 may be inconsistent because other threads have changed it. This makes it much more difficult to understand the code.

If used Node p = prev;那么#L0之后,就不需要同步p的值了。#L1到#L4的p is also consistent.

For volatile, see:
Java Language Spec volatile keyword
https://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.3.1.4

Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template