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.
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------------------------- 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...