简体   繁体   中英

Why not use values variable directly, the vs variable is not necessary

Here is from HashMap:

transient Collection<V> values;

public Collection<V> values() {
    Collection<V> vs = values;
    if (vs == null) {
        vs = new Values();
        values = vs;
    }
    return vs;
}

I wonder why not use member variable values directly? Why create the local variable named vs ?

How is that better than:

transient Collection<V> values;

public Collection<V> values() {
    if (values == null) {
        values = new Values();
    }
    return values;
}

Local variables are held on the stack and are very fast, faster than reading a field, even before the JVM optimizes the method (if it bothers to, the method may not be called enough in a given program). In the common case, values is not null , so it's retrieved once, then the local is tested and used as the return value. If you didn't use a local variable, you'd have to read the field twice (once for the null check, once for the return ). That isn't as fast as reusing the local.

There may also be a general style rule (related to efficiency) being applied that doesn't necessarily show its benefits in values() , specifically.


Let's look at it in more depth. Consider these two implementations of a values -like method, one using a local and the other using the field everywhere:

private transient Map<T, U> map1 = null;
public Map<T, U> likeValues() {
    Map<T, U> m = this.map1;
    if (m == null) {
        m = new HashMap<T, U>();
        this.map1 = m;
    }
    return m;
}

private transient Map<T, U> map2 = null;
public Map<T, U> usingField() {
    if (this.map2 == null) {
        this.map2 = new HashMap<T, U>();
    }
    return this.map2;
}

Let's look at the bytecode for them, I've added comments (they may not be perfect, I'm not a bytecode expert and moreover I'm going for clarity rather than describing each and every stack operation in detail):

public java.util.Map likeValues();
  Code:
     0: aload_0             // Load `this` onto the stack
     1: getfield      #2    // Get the field's value
     4: astore_1            // Store it in local variable 1
     5: aload_1             // Load local variable 1
     6: ifnonnull     22    // Jump if not null to #22
     9: new           #5    // Create the HashMap
    12: dup                 // Duplicate the top value on the stack
    13: invokespecial #6    // Call method java/util/HashMap."":()V to init the HashMap
    16: astore_1            // Store the result in local variable 1
    17: aload_0             // Load `this` onto the stack
    18: aload_1             // Load local variable 1 onto the stack
    19: putfield      #2    // Store the value in the field
    22: aload_1             // Load local variable 1 onto the stack
    23: areturn             // Return it

public java.util.Map usingField();
  Code:
     0: aload_0             // Load `this` onto the stack
     1: getfield      #3    // Get the field's value
     4: ifnonnull     18    // Jump if not null to #18
     7: aload_0             // Load `this` onto the stack
     8: new           #5    // Create the HashMap
    11: dup                 // Duplicate the top value on the stack                          
    12: invokespecial #6    // Call method java/util/HashMap."":()V to init the HashMap
    15: putfield      #3    // Store it in the field
    18: aload_0             // Load `this` onto the stack
    19: getfield      #3    // Get the field's value
    22: areturn             // Return it

Let's look only at the non- null path in those:

public java.util.Map likeValues();
  Code:
     0: aload_0             // Load `this` onto the stack
     1: getfield      #2    // Get the field's value
     4: astore_1            // Store it in local variable 1
     5: aload_1             // Load local variable 1
     6: ifnonnull     22    // Jump if not null to #22
    // ...skipped...
    22: aload_1             // Load local variable 1 onto the stack
    23: areturn             // Return it

public java.util.Map usingField();
  Code:
     0: aload_0             // Load `this` onto the stack
     1: getfield      #3    // Get the field's value
     4: ifnonnull     18    // Jump if not null to #18
    // ...skipped...
    18: aload_0             // Load `this` onto the stack
    19: getfield      #3    // Get the field's value
    22: areturn             // Return it

In likeValues , the field is only read once, and then the local variable (on the stack) is used for the comparison and return.

In usingField , the field is read twice. Reading a field is not as fast as using a stack value.

Although the two implementations look very similar, there is one major difference.

Let's say you are in a multi-threaded application and Thread 2 changes the reference to values while Thread 1 is executing the values() method. This is as easy as having both threads call the values() method roughly at the same time.

With the first implementation changing such value will have no effect on the value being returned by values() in a given thread as it comes from the vs variable. With the second implementation on the other hand there is a chance that the call to values() will yield something different from the outcome of new Values() as the return value comes from the shared values variable.

In other words, the HashMap version will behave consistently in a multi-threaded environment (note that I didn't call it "thread-safe", as that is normally used with a broader meaning) whereas your version will behave inconsistently, thus potentially causing problems that are very difficult to track down in multi-threaded applications.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM