Home of The JavaSpecialists' Newsletter

192Implicit Escape of "this"

Posted: 2011-05-31Category: ConcurrencyJava Version: 1.5+Dr. Heinz M. Kabutz
 

Abstract: We should never allow references to our objects to escape before all the final fields have been set, otherwise we can cause race conditions. In this newsletter we explore how this is possible to do.

 

Welcome to the 192nd issue of The Java(tm) Specialists' Newsletter, which I am writing underneath some plane trees on the 1821 square of Chania. Last week Kirk Pepperdine ran his performance course at our conference room, so we had our hands full showing the students Cretan hospitality. I managed to convince them to join me on a short walk to a beach that no one I knew had ever been to. After about an hour of scrambling down a ravine, over boulders and squeezing through dense vegetation, we realised that the light was fading and decided to rather head back again. It was a great adventure for my three kids, aged 4 to 12. I am going back this Friday with brother-in-law Cam to scout the place out properly.

We are running an open spaces conference here in Crete at the end of August. Please let us know if you are interested in attending. We have space for about another five people. The conference is free, but of course you need to pay your own transport here.

NEW: Refactoring to Java 8 Lambdas and Streams Workshop Are you currently using Java 6 or 7 and would like to see how Java 8 can improve your code base? Are you tired of courses that teach you a whole bunch of techniques that you cannot apply in your world? Check out our one day intensive Refactoring to Java 8 Lambdas and Streams Workshop.

Implicit Escape of "this"

A few weeks ago, one of my friends sent me a question about section 3.2 of Java Concurrency in Practice. He could not understand how a method in Class A with a reference to an inner class in class B could obtain a reference to the outer class B. My friend thought that Brian might have meant that you could access the object via reflection.

For those of you who do not own the book yet, here is Brian's statement from Section 3.2 (this is one book that you really want to own though):

Java Concurrency in Practice, Section 3.2: A final [heinz: Brian means "final" as in "last", not as in Java final] mechanism by which an object or its internal state can be published is to publish an inner class instance, as shown in ThisEscape in Listing 3.7. When ThisEscape publishes the EventListener, it implicitly publishes the enclosing ThisEscape instance as well, because inner class instances contain a hidden reference to the enclosing instance.

//-------------------------Listing 3.7-------------------------
public class ThisEscape {
  public ThisEscape(EventSource source) {
    source.registerListener(
      new EventListener() {
        public void onEvent(Event e) {
          doSomething(e);
        }
      });
  }
}
  

Since my good friend was puzzling about this, I decided to expand the ThisEscape class with its own fields and doSomething() method and some additional code to demonstrate a possible data race.

In my class, I have a final field num that is initialized in the constructor. However, before it is set to 42, we register an anonymous inner class, which also leaks a pointer to the enclosing object.

import java.util.*;

public class ThisEscape {
  private final int num;

  public ThisEscape(EventSource source) {
    source.registerListener(
        new EventListener() {
          public void onEvent(Event e) {
            doSomething(e);
          }
        });
    num = 42;
  }

  private void doSomething(Event e) {
    if (num != 42) {
      System.out.println("Race condition detected at " +
          new Date());
    }
  }
}    
  

In my example, the Event and EventListener classes are kept as simple as possible:

public class Event { }
public interface EventListener {
  public void onEvent(Event e);
}
  

The EventSource is more complicated. In our case it is a Thread that repeatedly sends events to its latest listeners. Since we are trying to produce the race condition, we only ever send an event to a listener once. Thus the registerListener() method appends it to the end of the listeners queue and it is then taken off by the take() method call within the run() method of the thread.

import java.util.concurrent.*;

public class EventSource extends Thread {
  private final BlockingQueue<EventListener> listeners =
      new LinkedBlockingQueue<EventListener>();

  public void run() {
    while (true) {
      try {
        listeners.take().onEvent(null);
      } catch (InterruptedException e) {
        break;
      }
    }
  }

  public void registerListener(EventListener eventListener) {
    listeners.add(eventListener);
  }
}
  

All that is left is to construct a lot of ThisEscape objects in a row and watch the wheels come off:

public class ThisEscapeTest {
  public static void main(String[] args) {
    EventSource es = new EventSource();
    es.start();
    while(true) {
      new ThisEscape(es);
    }
  }
}
  

On my machine, I get race conditions immediately. What is interesting is that they stopped for a while and then started up again. However, I think this was probably a capacity problem with using a queue for the listeners. It would maybe be better to have a queue of capacity 1, in which case we could use an AtomicReference instead:

import java.util.concurrent.atomic.*;

public class EventSource2 extends Thread {
  private final AtomicReference<EventListener> listeners =
      new AtomicReference<EventListener>();

  public void run() {
    while (true) {
      EventListener listener = listeners.getAndSet(null);
      if (listener != null) {
        listener.onEvent(null);
      }
    }
  }

  public void registerListener(EventListener eventListener) {
    listeners.set(eventListener);
  }
}
  

Now we see the race conditions immediately and they do not stop when the queue gets too long.

Our lesson to learn is: We should never allow references to our objects to escape before all the final fields have been set, otherwise we can cause race conditions.

Heinz

P.S. One of our readers, Ulf, decided to log a bug against this behavior. He thinks that the compiler should detect and warn about such race conditions. Feel free to vote on this issue if you like.

 

Related Articles

Browse the Newsletter Archive

About the Author

demo

Java Champion, author of the Javaspecialists Newsletter, conference speaking regular... About Heinz

Java Training

We deliver relevant courses, by top Java developers to produce more resourceful and efficient programmers within their organisations.

Java Consulting

Nobody ever wants to call a Java performance consultant, but with first-hand experience repairing and improving commercial Java applications - JavaSpecialists are a good place to start...

Threading Emergency?

If your system is down, we will review it for 15 minutes and give you our findings for just 1 € without any obligation.