1

here I have found the following code how to clean Thread's ThreadLocals in Java:

private void cleanThreadLocals() {
    try {
        // Get a reference to the thread locals table of the current thread
        Thread thread = Thread.currentThread();
        Field threadLocalsField = Thread.class.getDeclaredField("threadLocals");
        threadLocalsField.setAccessible(true);
        Object threadLocalTable = threadLocalsField.get(thread);

        // Get a reference to the array holding the thread local variables inside the
        // ThreadLocalMap of the current thread
        Class threadLocalMapClass = Class.forName("java.lang.ThreadLocal$ThreadLocalMap");
        Field tableField = threadLocalMapClass.getDeclaredField("table");
        tableField.setAccessible(true);
        Object table = tableField.get(threadLocalTable);

        // The key to the ThreadLocalMap is a WeakReference object. The referent field of this object
        // is a reference to the actual ThreadLocal variable
        Field referentField = Reference.class.getDeclaredField("referent");
        referentField.setAccessible(true);

        for (int i=0; i < Array.getLength(table); i++) {
            // Each entry in the table array of ThreadLocalMap is an Entry object
            // representing the thread local reference and its value
            Object entry = Array.get(table, i);
            if (entry != null) {
                // Get a reference to the thread local object and remove it from the table
                ThreadLocal threadLocal = (ThreadLocal)referentField.get(entry);
                threadLocal.remove();
            }
        }
    } catch(Exception e) {
        // We will tolerate an exception here and just log it
        throw new IllegalStateException(e);
    }
}

It is quite complicated. What about following simpler code, is it sufficient to clean ThreadLocals? Thank you.

private void cleanThreadLocals(Thread thread) {
    try {
        // Get a reference to the thread locals table of the current thread
        Thread thread = Thread.currentThread();
        Field threadLocalsField = Thread.class.getDeclaredField("threadLocals");
        threadLocalsField.setAccessible(true);
        threadLocalsField.set(thread, null);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}
Ladislavovic
  • 75
  • 1
  • 5

1 Answers1

0

Both snippets clean ThreadLocals, but they have different approach.

The longer snippet first get all thread ThreadLocals from ThreadLocalMap and call remove() method on them. You can use it only for the current thread, because remove() method just remove() value from current thread, there is no way how to specify which thread it should work with. With some modifications you could use it for any thread, you would have to call remove() on ThreadLocalMap directly.

The shorter snippet removes whole ThreadLocalMap instance from Thread. It is lazily initialized so it will be created again if needed. You can use this approach for any Thread.

I tested if all instances are removed from JVM. The test rely on GC behavior you maybe need to tune GC behaviour on some environment, but on my environment win10/OracleJava8 it pass out of the box.

Test:

@Test
public void howToCleanThreadLocalValues() throws ReflectiveOperationException {
    Thread thread = Thread.currentThread();

    // Set thread local value for current thread
    WeakReference<ThreadLocal> threadLocal = new WeakReference<>(new ThreadLocal<>());
    threadLocal.get().set("foo");

    // Get ThreadLocalMap
    Field threadLocalsField = Thread.class.getDeclaredField("threadLocals");
    threadLocalsField.setAccessible(true);
    WeakReference<Object> threadLocalMap = new WeakReference<>(threadLocalsField.get(thread));
    Assert.assertNotNull(threadLocalMap.get());
    Assert.assertNotNull(threadLocal.get());

    // Set ThreadLocalMap to null, GC do the rest
    threadLocalsField.set(Thread.currentThread(), null);
    System.gc();
    Assert.assertNull(threadLocalMap.get());
    Assert.assertNull(threadLocal.get());
}
Ladislavovic
  • 75
  • 1
  • 5