Reputation: 78491
Is there a way to list TheadLocals bound to a thread? Ideally I could access the Thread.threadLocals map, but it is package protected.
The reason I need this is I need to inspect threads as they are returned to a thread pool to ensure the ThreadLocals have been properly cleaned up. Perhaps there is another way to do this?
Upvotes: 15
Views: 9865
Reputation: 36164
Along the lines of 'Another good way to do this', I made a Runnable wrapper which takes a snapshot of the pre-existing thread locals, runs the nested runnable, then clears (sets to null) any thread local which was not initially present.
This could be better done by putting the 'snapshot' code into a subclassed Executor's beforeExecute() and the 'cleanup' code in the afterExecute as suggested by @danben.
Either way, the beauty is that you don't have to hard-code which thread locals to keep or discard.
Exception handling was removed from the source listing to avoid clutter.
public class ThreadLocalCleaningRunnable implements Runnable
{
private final Runnable runnable;
public ThreadLocalCleaningRunnable(Runnable runnable) {
this.runnable = nonNull(runnable);
}
public void run() {
// printThreadLocals();
Set<ThreadLocal<?>> initialThreadLocalKeys = getThreadLocalKeys();
try {
runnable.run();
}
finally {
cleanThreadLocalsExcept(initialThreadLocalKeys);
// printThreadLocals();
}
}
public static void printThreadLocals() {
Thread thread = Thread.currentThread();
Field threadLocalsField = Thread.class.getDeclaredField("threadLocals");
threadLocalsField.setAccessible(true);
Class<?> threadLocalMapKlazz = Class.forName("java.lang.ThreadLocal$ThreadLocalMap");
Field tableField = threadLocalMapKlazz.getDeclaredField("table");
tableField.setAccessible(true);
Object threadLocals = threadLocalsField.get(thread);
if (threadLocals != null) {
Object table = tableField.get(threadLocals);
if (table != null) {
int threadLocalCount = Array.getLength(table);
String threadName = thread.getName();
for (int i = 0; i < threadLocalCount; i++) {
Object entry = Array.get(table, i);
if (entry instanceof Reference) {
Field valueField = entry.getClass().getDeclaredField("value");
valueField.setAccessible(true);
Object value = valueField.get(entry);
if (value != null) {
System.err.print("thread ["+ threadName +"] local "+ value.getClass().getName() +" "+ value.toString());
}
}
}
}
}
}
public static Set<ThreadLocal<?>> getThreadLocalKeys() {
Thread thread = Thread.currentThread();
Set<ThreadLocal<?>> threadLocalKeys = new HashSet<ThreadLocal<?>>();
Field threadLocalsField = Thread.class.getDeclaredField("threadLocals");
threadLocalsField.setAccessible(true);
Class<?> threadLocalMapKlazz = Class.forName("java.lang.ThreadLocal$ThreadLocalMap");
Field tableField = threadLocalMapKlazz.getDeclaredField("table");
tableField.setAccessible(true);
Object threadLocals = threadLocalsField.get(thread);
if (threadLocals != null) {
Object table = tableField.get(threadLocals);
if (table != null) {
int threadLocalCount = Array.getLength(table);
for (int i = 0; i < threadLocalCount; i++) {
Object entry = Array.get(table, i);
if (entry instanceof Reference) {
Object o = ((Reference<?>) entry).get();
if (o instanceof ThreadLocal) {
threadLocalKeys.add((ThreadLocal<?>) o);
}
}
}
}
}
return threadLocalKeys;
}
public static void cleanThreadLocalsExcept(Set<ThreadLocal<?>> keptThreadLocalKeys) {
Thread thread = Thread.currentThread();
Field threadLocalsField = Thread.class.getDeclaredField("threadLocals");
threadLocalsField.setAccessible(true);
Class<?> threadLocalMapKlazz = Class.forName("java.lang.ThreadLocal$ThreadLocalMap");
Field tableField = threadLocalMapKlazz.getDeclaredField("table");
tableField.setAccessible(true);
Object threadLocals = threadLocalsField.get(thread);
if (threadLocals != null) {
Object table = tableField.get(threadLocals);
if (table != null) {
int threadLocalCount = Array.getLength(table);
for (int i = 0; i < threadLocalCount; i++) {
Object entry = Array.get(table, i);
if (entry instanceof Reference) {
Object o = ((Reference<?>) entry).get();
if (o instanceof ThreadLocal) {
ThreadLocal<?> tl = (ThreadLocal<?>) o;
if (!keptThreadLocalKeys.contains(tl)) {
Field valueField = entry.getClass().getDeclaredField("value");
valueField.setAccessible(true);
valueField.set(entry, null);
}
}
}
}
}
}
}
}
Upvotes: 2
Reputation: 800
Listing ThreadLocals and Clearing ThreadLocals can be accomplished by using reflection (and the setAccessible() flag) to override the JVM's usual permissions. For obvious reasons, this not available when all security mechanisms are in place.
Upvotes: 13
Reputation: 16528
From the sources, it looks to be pretty tight. Everything is private to either Thread or ThreadLocal.
You might be able to do what you need through an instrumentation agent by redefining ThreadLocal to add a method that will dump the locals on the current thread.
Here's an example I found for adding logging to an existing class: http://today.java.net/pub/a/today/2008/04/24/add-logging-at-class-load-time-with-instrumentation.html
You'll need to use BCEL or JavaAssist to patch ThreadLocal bytecode to add the method. Later on, you'll need to use reflection to get a handle to the method so you can call it.
Note: this probably won't work if you are running in a restricted environment (applet or appserver) as the security mechanisms generally prevent you from mucking about with system classes.
Upvotes: 0
Reputation: 1468
You could use AOP-like construct, by creating a Runnable implementation that wraps the original Runnable by your own implementation. It will invoke the original Runnable's run method and then perform any other cleanup you need from within the thread's context, which will allow you to call the ThreadLocal.remove() method. Then, give this wrapper to the thread pool. This would work for any thread pool implementation (e.g. ones without before/afterExecute methods)
Upvotes: 0
Reputation: 564821
The reason I need this is I need to inspect threads as they are returned to a thread pool to ensure the ThreadLocals have been properly cleaned up.
Personally, I think using thread local data on threadpool threads is a bad practice. If you really need thread local state, you should be self managing the threads, so you can explicitly cleanup the data.
Thread pool threads have indeterminate lifetimes, so you shouldn't be relying on explicitly managed local thread data.
Upvotes: 1
Reputation: 83310
You could use the afterExecute
method of the thread pool to do whatever cleanup (reinitializiation?), as long as you know which variables you want to clean up.
Otherwise you could use reflection - from within the thread, iterate through the declared fields of the class(es) youre interested in, and for each one whose type is an instance of ThreadLocal, set
it to its initialValue
on the object(s) you care about.
Upvotes: 2