13

Given that a ThreadLocal variable holds different values for different threads, is it possible to access the value of one ThreadLocal variable from another thread?

I.e. in the example code below, is it possible in t1 to read the value of TLocWrapper.tlint from t2?

public class Example
{
  public static void main (String[] args)
  {
    Tex t1 = new Tex("t1"), t2 = new Tex("t2");
    new Thread(t1).start();
    try
    {
      Thread.sleep(100);
    }
    catch (InterruptedException e)
    {}
    new Thread(t2).start();
    try
    {
      Thread.sleep(1000);
    }
    catch (InterruptedException e)
    {}
    t1.kill = true;
    t2.kill = true;
  }

  private static class Tex implements Runnable
  {
    final String name;
    Tex (String name)
    {
      this.name = name;
    }
    public boolean kill = false;
    public void run ()
    {
      TLocWrapper.get().tlint.set(System.currentTimeMillis());
      while (!kill)
      {
        // read value of tlint from TLocWrapper
        System.out.println(name + ": " + TLocWrapper.get().tlint.get());
      }
    }
  }
}
class TLocWrapper
{
  public ThreadLocal<Long> tlint = new ThreadLocal<Long>();
  static final TLocWrapper self = new TLocWrapper();
  static TLocWrapper get ()
  {
    return self;
  }
  private TLocWrapper () {}
}
JHollanti
  • 2,343
  • 8
  • 27
  • 39
  • 1
    A regular variable with the appropriate read/write locking is used to share data between threads. A ThreadLocal is specifically made in the cases where you /don't/ want to share data between threads. This makes me believe that either this is a purely hypothetical question, or that you're trying to do something with ThreadLocal it was specifically intended not to be used for. – cthulhu Mar 03 '11 at 11:46
  • 1
    @Cthulhu: Yep, i'm trying to do something "evil" with ThreadLocal--but my intentions are good :). I'm only trying to get past an issue. – JHollanti Mar 03 '11 at 12:23
  • BTW: You have to make the kill field volatile, this is a very subtle bug. – sleeplessnerd Oct 24 '14 at 02:11

5 Answers5

16

As Peter says, this isn't possible. If you want this sort of functionality, then conceptually what you really want is just a standard Map<Thread, Long> - where most operations will be done with a key of Thread.currentThread(), but you can pass in other threads if you wish.

However, this likely isn't a great idea. For one, holding a reference to moribund threads is going to mess up GC, so you'd have to go through the extra hoop of making the key type WeakReference<Thread> instead. And I'm not convinced that a Thread is a great Map key anyway.

So once you go beyond the convenience of the baked-in ThreadLocal, perhaps it's worth questioning whether using a Thread object as the key is the best option? It might be better to give each threads unique IDs (Strings or ints, if they don't already have natural keys that make more sense), and simply use these to key the map off. I realise your example is contrived, but you could do the same thing with a Map<String, Long> and using keys of "t1" and "t2".

It would also arguably be clearer since a Map represents how you're actually using the data structure; ThreadLocals are more like scalar variables with a bit of access-control magic than a collection, so even if it were possible to use them as you want it would likely be more confusing for other people looking at your code.

Andrzej Doyle
  • 102,507
  • 33
  • 189
  • 228
  • Why would Thread not be a good Map key? And is using Map really better? When threads terminate, you will leak data in your map. Even with Map,V>, you won't leak threads, but you will leak thread-local data. I suggest, creating a holder object to hold V, and then storing the holder twice: once in a normal ThreadLocal, and then again in a synchronized or concurrent map indexed by Thread for access by other threads. Then create a cleaner daemon thread that periodically removes from the map dead threads. – Simon Kissane Sep 26 '13 at 23:14
12

Based on the answer of Andrzej Doyle here a full working solution:

ThreadLocal<String> threadLocal = new ThreadLocal<String>();
threadLocal.set("Test"); // do this in otherThread

Thread otherThread = Thread.currentThread(); // get a reference to the otherThread somehow (this is just for demo)

Field field = Thread.class.getDeclaredField("threadLocals");
field.setAccessible(true);
Object map = field.get(otherThread);

Method method = Class.forName("java.lang.ThreadLocal$ThreadLocalMap").getDeclaredMethod("getEntry", ThreadLocal.class);
method.setAccessible(true);
WeakReference entry = (WeakReference) method.invoke(map, threadLocal);

Field valueField = Class.forName("java.lang.ThreadLocal$ThreadLocalMap$Entry").getDeclaredField("value");
valueField.setAccessible(true);
Object value = valueField.get(entry);

System.out.println("value: " + value); // prints: "value: Test"

All the previous comments still apply of course - it's not safe!

But for debugging purposes it might be just what you need - I use it that way.

Frederic Leitenberger
  • 1,949
  • 24
  • 32
7

I wanted to see what was in ThreadLocal storage, so I extended the above example to show me. Also handy for debugging.

            Field field = Thread.class.getDeclaredField("threadLocals");
            field.setAccessible(true);
            Object map = field.get(Thread.currentThread());
            Field table = Class.forName("java.lang.ThreadLocal$ThreadLocalMap").getDeclaredField("table");
            table.setAccessible(true);
            Object tbl = table.get(map);
            int length = Array.getLength(tbl);
            for(int i = 0; i < length; i++) {                   
                Object entry = Array.get(tbl, i);
                Object value = null;
                String valueClass = null;
                if(entry != null) { 
                    Field valueField = Class.forName("java.lang.ThreadLocal$ThreadLocalMap$Entry").getDeclaredField("value");
                    valueField.setAccessible(true);
                    value = valueField.get(entry);
                    if(value != null) {
                        valueClass = value.getClass().getName();
                    }
                    Logger.getRootLogger().info("[" + i + "] type[" + valueClass + "] " + value);
                }
            }
WizardsOfWor
  • 2,974
  • 29
  • 23
6

ThreadLocalMap CAN be access via Reflection and Thread.class.getDeclaredField("threadLocals") setAccssible(true), and so on. Do not do that, though. The map is expected to be accessed by the owning thread only and accessing any value of a ThreadLocal is a potential data race.

However, if you can live w/ the said data races, or just avoid them (way better idea). Here is the simplest solution. Extend Thread and define whatever you need there, that's it:

ThreadX extends Thread{
  int extraField1;
  String blah2; //and so on
}

That's a decent solution that doesn't relies on WeakReferences but requires that you create the threads. You can set like that ((ThreadX)Thread.currentThread()).extraField1=22

Make sure you do no exhibit data races while accessing the fields. So you might need volatile, synchronized and so on.

Overall Map is a terribad idea, never keep references to object you do not manage/own explicitly; especially when it comes to Thread, ThreadGroup, Class, ClassLoader... WeakHashMap<Thread, Object> is slightly better, however you need to access it exclusively (i.e. under lock) which might damper the performance in heavily multithreaded environment. WeakHashMap is not the fastest thing in the world.

ConcurrentMap, Object> would be better but you need a WeakRef that has equals and hashCode...

bestsss
  • 11,796
  • 3
  • 53
  • 63
5

It only possible if you place the same value in a field which is not ThreadLocal and access that instead. A ThreadLocal by definition is only local to that thread.

Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130