Running on Java 24-ea+28-3562 (Preview)
Home of The JavaSpecialists' Newsletter

192Implicit Escape of "this"

Author: Dr. Heinz M. KabutzDate: 2011-05-31Java Version: 5Category: Concurrency
 

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.

javaspecialists.teachable.com: Please visit our new self-study course catalog to see how you can upskill your Java knowledge.

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.

 

Comments

We are always happy to receive comments from our readers. Feel free to send me a comment via email or discuss the newsletter in our JavaSpecialists Slack Channel (Get an invite here)

When you load these comments, you'll be connected to Disqus. Privacy Statement.

Related Articles

Browse the Newsletter Archive

About the Author

Heinz Kabutz Java Conference Speaker

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

Superpack '23

Superpack '24 Our entire Java Specialists Training in one huge bundle more...

Free Java Book

Dynamic Proxies in Java Book
Java Training

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

Java Consulting

We can help make your Java application run faster and trouble-shoot concurrency and performance bugs...