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:
- Creating a Calendar is non-trivial (various calculations and
accesses to localisation resources need to be made each time one is created);
- There's no actual requirement to share Calendars between
threads or have fewer calendars than threads.
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.