Landon Kuhn
Landon Kuhn

Reputation: 78491

java: list thread locals?

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

Answers (6)

karmakaze
karmakaze

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

Jarek Rozanski
Jarek Rozanski

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

Devon_C_Miller
Devon_C_Miller

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

Armadillo
Armadillo

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

Reed Copsey
Reed Copsey

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

danben
danben

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

Related Questions