Thread-local variables in Java (ctd)

Example: using ThreadLocal to re-use Calendar objects

So when in practice would we use ThreadLocal? A typical example of using ThreadLocal would be as an alternative to an object or resource pool, when we don't mind creating one object per thread. Let's consider the example of a pool of Calendar instances. In an application that does a lot of date manipulation, Calendar classes may be a good candidates for pooling because:

One (inefficient) way to re-use Calendars would be to create a 'calendar pool' class such as this:

public class CalendarFactory {
  private List calendars = new ArrayList();
  private static CalendarFactory instance = new CalendarFactory();

  public static CalendarFactory getFactory() { return instance; }

  public Calendar getCalendar() {
    synchronized (calendars) {
      if (calendars.isEmpty()) {
        return new GregorianCalendar();
      } else {
        return calendars.remove(calendars.size()-1);
      }
    }
  }
  public void returnCalendar(Calendar cal) {
    synchronized (calendars) {
      calendars.add(cal);
    }
  }
  // Don't let outsiders create new factories directly
  private CalendarFactory() {}
}

Then a client could call:

Calendar cal = CalendarFactory.getFactory().getCalendar();
try {
  // perform some calculation using cal
} finally {
  CalendarFactory.getFactory().returnCalendar(cal);
}

This would allow us to re-use Calendar objects but it's a bit inefficient because in each case it makes two synchronized calls when we're actually not that bothered about sharing the calendars across the threads. We wouldn't care, for example, if Thread 1 created and finished with a calendar and then Thread 2 created another, even though Thread 1 had finished with its. The number of threads will typically be small-ish (maybe in the hundreds at the very most), and so having as many calendars knocking around as there are threads is an OK compromise. (This would not be the case, for example, with database connections: if possible, we generally would want Thread 2 to use the connection that Thread 1 had just finished with rather than creating another; in such cases, thread-local variables aren't the right solution.) Now let's see how we can improve CalendarFactory using ThreadLocal:

public class CalendarFactory {
  private ThreadLocal<Calendar> calendarRef = new ThreadLocal<Calendar>() {
    protected Calendar initialValue() {
      return new GregorianCalendar();
    }
  };
  private static CalendarFactory instance = new CalendarFactory();

  public static CalendarFactory getFactory() { return instance; }

  public Calendar getCalendar() {
    return calendarRef.get();
  }

  // Don't let outsiders create new factories directly
  private CalendarFactory() {}
}

Note that there is still a single, static instance of CalendarFactory shared by all threads. But that single instance uses the ThreadLocal variable calendarRef, which has a per-thread value. Inside getCalendar(), the call to calendarRef.get() will always operate on our thread-private "instance" of the variable, and we don't need any synchronization.

This example uses the Java 5 generics feature: we declare ThreadLocal as 'containing' Calendar objects, so that the subsequent get() method doesn't need an explicit cast. (That is, the cast is inserted automatically by the compiler.) Another feature of this example is the initialValue() method. We actually subclass ThreadLocal and override initialValue() to provide an appropriate object each time a new one is required (i.e. when get() is called for the first time on a particular thread). We could of course have simply checked for null inside the getCalendar() method (the first time get() is called on a ThreadLocal for a particular thread, it returns null) and set the value if it was null:

  public Calendar getCalendar() {
    Calendar cal = calendarRef.get();
    if (cal == null) {
      calendarRef.set(cal = new GregorianCalendar());
    }
    return cal;
  }

However, overriding ThreadLocal.initialValue() automatically handles this logic and makes our code a bit neater– especially if we're calling get() in multiple places.

Note that we don't have a "return to pool" method. Once created, we let the calendar hang around for as long as the thread is alive1. If we really wanted to remove these instances at a particular moment, then as of Java 5, we can call calendarRef.remove(), which removes the calling thread's value; it would be set to the initial value again on the next call to calendarRef.get().

Next...

On the next page, we look at general criteria for deciding when to use ThreadLocal.

Notes
1. All values set on a ThreadLocal also become garbage collectable if the ThreadLocal becomes no longer reachable outside of the Thread class. In other words, a thread's map of ThreadLocal to value holds on to the ThreadLocal only via a weak reference.


If you enjoy this Java programming article, please share with friends and colleagues. Follow the author on Twitter for the latest news and rants.

Editorial page content written by Neil Coffey. Copyright © Javamex UK 2021. All rights reserved.