10

I want to test if a ThreadLocal has been initialized without actually initializing it. Of course, the code needs to be thread-safe. Ideally I want something like this:

class TestableThreadLocal<T> extends ThreadLocal<T> {
    public boolean isInitialized() {
        ...
    }
}

But how would I implement this method?

Edit: Motivation: I have subclassed a ThreadLocal to override initialValue(). However, I do not always need the initialization, in particular because it could cause a memory leak in multi-classloader environments. A simple test would help me write code to avoid the accidental initialization.

vektor
  • 3,312
  • 8
  • 41
  • 71
Christian Schlichtherle
  • 3,125
  • 1
  • 23
  • 47
  • Without initializing it. – Christian Schlichtherle Jul 14 '12 at 13:26
  • what about using a central set to hold all initialized ThreadLocals? – oshai Jul 14 '12 at 15:13
  • @oshai: This is the way ThreadLocals are implemented in Threads. However, if I go the same route, wouldn't I end up with the same memory leak problems in multi-classloader environments? – Christian Schlichtherle Jul 15 '12 at 22:09
  • you should to give an example of the leak in multi-classloader environments, so I will be sure we are talking about the same thing. you can use weak references for example. – oshai Jul 17 '12 at 18:57
  • 1
    @oshai, ThreadLocals are amongst the greatest classloader-leaks offenders: http://stackoverflow.com/questions/6470651/creating-a-memory-leak-with-java/6540248#6540248 I'd say #1 offenders and I do a quite a lot of middleware code. – bestsss Aug 06 '12 at 10:35

4 Answers4

11

I tend to use simple rules to avoid classloader leaks, admittedly they are cumbersome but with some wrapping it's not that bad. Yes, leaks are evil.

  • do no override initialValue, ever - you are just asking for trouble, just forget it exists.
  • do not store non-system/non-bootstap classes in the ThreadLocal unless you do threadLocal.set(xxx); try{...process...}finally{threadLocal.set(null);}
  • if you still override initialValue, use threadLocal.remove() not threadLocal.set(null)
  • or use WeakReferene(i.e. ThreadLocal<WeakReference<Foo>>) for the value from a pool that keeps the hard references. It might look counter intuitive but once the pool is cleared the values disappear and the classes are free to be GC'd

I realize the post is not a direct reply to your question, however there is no simple way to achieve what you wish and keep the leaks at bay.

bestsss
  • 11,796
  • 3
  • 53
  • 63
  • To tell the full truth, in my case I am using an InheritableThreadLocal and I definitely need to override childValue(), but I will check if applying the rest of your tips will help. – Christian Schlichtherle Aug 06 '12 at 12:05
  • @Christian, InheritableThreadLocal is way, way worse. Indeed you need childValue and returning null. Another tip -> use ThreadGroup(!!) instead of InheritableMadness. The latter is almost guaranteed leak, new threads alone are quite a hazard but InheritableThreadLocal needs a lot of planning. I'd strongly recommend against any 3rd party lib relying on them. More on new threads: http://stackoverflow.com/a/4730500/554431 – bestsss Aug 06 '12 at 12:20
  • @Christian, did any of the points above helped or you just "had" to select an answer. – bestsss Aug 13 '12 at 23:24
  • While your answer wasn't directly solving my question, it helped me rethink my approach and fix the memory leak. The result can be seen here: http://java.net/projects/truezip/sources/v7/content/truezip-kernel/src/main/java/de/schlichtherle/truezip/util/InheritableThreadLocalStack.java?rev=5535 – Christian Schlichtherle Aug 14 '12 at 06:33
  • The option to use a `WeakReference` is suitable only for values one can loose. The garbage collector can erase them any time, if they are not hard-referenced from somewhere else. – Martin Jun 25 '18 at 13:00
2

Thoughts:

  • if ThreadLocal.get is called, the value is initialized.
  • If you don't want it initialized, you don't need the value stored (at all, ever)
  • If you want to avoid the initialize, it seems the place to start is by overriding get, not initialValue.
  • taking a different track - do you really need to inherit ThreadLocal as opposed to containment?
Richard Sitze
  • 8,262
  • 3
  • 36
  • 48
1

One of the few possible ways of doing this would involve reflection as ThreadLocal does not have an API that lets you know whether the value has been initialized.

It is certainly possible to "code" it using reflection. However, all the usual caveat applies. Your reflection code would be highly dependent on the implementation details of java.lang.ThreadLocal and its non-public members. The moment the JDK vendors change the implementation your code would break.

sjlee
  • 7,726
  • 2
  • 29
  • 37
1

I am confused by your initial question. Is the ThreadLocal one that you are creating or it is already provided by someone else API?

If you are making the ThreadLocal, then I would assume you already know you have to override initialValue() so that you can provide the initial value.

In that case you can always track if that has been called and the value has been created.

I think something like this code below will do what you want:

MyThreadLocal<T> extends ThreadLocal<T>{

   Map<Thread,Boolean> myThreadMap = new HashMap<Thread,Boolean>();

   protected T initialValue(){
         synchronized(myThreadMap){
               T value =  ... your code to create initial value
               myThreadMap.put(Thread.currentThread(),true);
               return T;
         }
   }

   public boolean containsThreadValues(){
          synchronized(myThreadMap){
                 return myThreadMap.isEmpty();
          }
   }

   public boolean isInitializedForThisThread(){
         synchronized(myThreadMap){
                 return myThreadMap.get(Thread.currentThread())==true;
         }
   }


}

The myThreadMap will always contain old keys, even for dead threads. If you want old keys purged, then replace it with a WeakHashMap. Then the keys will be removed as the Threads that once used the ThreadLocal pass away.

The Coordinator
  • 13,007
  • 11
  • 44
  • 73