MsA
MsA

Reputation: 2979

Understanding InheritableThreadLocal in Java

I wanted a ParentThread to have threadId set to the value (say p1) passed to its constructor. Then all its child have threadId set to p1.c1, p1.c2 and so on. I wrote following code:

public class InheritableThreadLocalDemo {

    public static void main(String[] args) 
    { 
        ParentThread pt = new ParentThread("p1"); 
        pt.start(); 
    } 
}

class ParentThread extends Thread { 
    static int childCount = 0;

    public static InheritableThreadLocal threadId = new InheritableThreadLocal() {
        public Object childValue(Object parentValue) 
        { 
            return parentValue.toString() + ".c" + (++childCount) ;  //this is line 18 where NullPointerException is occuring
        }
    }; 

    public ParentThread(String pThreadId) {
        threadId.set(pThreadId);
    }

    public void run() 
    { 
        System.out.println("Thread id:" + threadId.get());

        ChildThread childThread1 = new ChildThread(); 
        childThread1.start(); 

        ChildThread childThread2 = new ChildThread(); 
        childThread2.start();
    } 
} 
class ChildThread extends Thread { 

    public void run() 
    { 
        System.out.println("Child Thread Value :" + ParentThread.threadId.get()); 
    } 
} 

It prints:

Thread id:null
Exception in thread "Thread-0" java.lang.NullPointerException
    at com.mahesha999.examples.java.multithreading.ParentThread$1.childValue(InheritableThreadLocalDemo.java:18)
    at java.lang.ThreadLocal$ThreadLocalMap.<init>(Unknown Source)
    at java.lang.ThreadLocal$ThreadLocalMap.<init>(Unknown Source)
    at java.lang.ThreadLocal.createInheritedMap(Unknown Source)
    at java.lang.Thread.init(Unknown Source)
    at java.lang.Thread.init(Unknown Source)
    at java.lang.Thread.<init>(Unknown Source)
    at com.mahesha999.examples.java.multithreading.ChildThread.<init>(InheritableThreadLocalDemo.java:37)
    at com.mahesha999.examples.java.multithreading.ParentThread.run(InheritableThreadLocalDemo.java:30)

I debuged in eclipse and found that inside constructor threadId is correctly set to p1, but inside ParentThread.run(), its read as null. Also, inside childValue , .toString() is called on null, throwing NullPointerException.

Q1. *Why the value set in constructor is not visible inside run() and childValue()?

Also I want this ParentClass reusable, that is I should be able to have series like

I feel, for this, I should make childCount and threadId non-static. But removing static from both these variables gives me compile time error

Cannot make a static reference to the non-static field ParentThread.threadId

inside ChildThread.run() for ParentThread.threadId.get(). But, now I am not able to imagine how can I make these InheritableThreadLocal variables unique per thread per instance.

Q2. How should I do this?

Q3. Or am missing something stupid here? Is "unique per Thread per instance" is nothing but "unique per instance only" and we dont need the concept of thread-local at all in this case? Is this why all ThreadLocal variables should be static by convention?

Upvotes: 2

Views: 12425

Answers (2)

John H
John H

Reputation: 712

I think your confusion is thinking a ThreadLocal is associated with a Thread object that you embed it in. It is associated with whatever the current thread is when the thread local methods are called. Your have 4 threads here:

  1. Main Thread
  2. Parent Thread
  3. Child Thread #1
  4. Child Thread #2

The constructor that calls the thread local set method for the Parent Object is called within the Main Thread (#1), setting the thread local for that thread not the Parent Thread (#2) because it has not yet started running.

Here's a minimal working example without overriding anything in the thread class:

package foo;

import java.util.concurrent.atomic.AtomicInteger;

public class InheritableThreadLocalDemo {

    public static void main(String[] args) {

        final AtomicInteger childCount = new AtomicInteger();

        final InheritableThreadLocal<String> local = new InheritableThreadLocal<String>() {
            public String childValue(String parentValue) {
                return parentValue.toString() + ".c" + (childCount.incrementAndGet());
            }
        };

        final Runnable child = () -> {
            System.out.println("Child Thread Value: " + local.get());
        };

        final Runnable parent = () -> {
            // The thread local value is associated with the thread that is running this block of
            // code.
            local.set("p1");
            System.out.println("Thread id:" + local.get());

            Thread c1 = new Thread(child);
            c1.start();

            Thread c2 = new Thread(child);
            c2.start();
        };

        final Thread parentThread = new Thread(parent);
        parentThread.run();
    }
}

In this example there is a single thread local instance that is being shared between the child and parent Runnables. Under the hood these lambdas store the locally referenced threadLocal as a member field which you can inspect during debugging. But the scoping aspect to this is separate.

Think of a thread-local like a key into per-thread map structure.

class Thread {
    Map<ThreadLocal, Object> threadLocals;
    // getter
    Map<ThreadLocal, Object> getThreadLocals() { return threadLocals; }
}

Each different ThreadLocal instance is itself a key into this structure. The current thread is defined as the result of the call Thread.currentThread(). So, think of every time you call threadLocalInstance.get() it is doing this:

Object thisThreadValue = Thread.currentThread().getThreadLocals().get(threadLocalInstance);

Upvotes: 3

Tom Hawtin - tackline
Tom Hawtin - tackline

Reputation: 147164

Let's look at the relevant sections of code.

class ParentThread extends Thread { 
    // ...
    public ParentThread(String pThreadId) {
        threadId.set(pThreadId);
    }

So threadId will be set for the Thread.currentThread() that runs the constructor. This cannot be the instance that is being constructed.

    public void run() 
    { 
        System.out.println("Thread id:" + threadId.get());

Now in presumably the new thread, threadId will not be initialised.

        ChildThread childThread1 = new ChildThread(); 

And here the null is dereferenced.

Generally I would avoid ThreadLocal, and most other things associated with global variables. InheritableThreadLocal I would run screaming from though it has provided entertainment.

Upvotes: 0

Related Questions