javamultithreadingthread-localinheritable-thread-local

Unable to set value to InheritableThreadLocal field in class inherited Thread


Somehow when I try to set value to the InheritableThreadLocal field while creating an object of class that inherites Thread I get strange behavior: my object doesn't have the value I set while invoking constructor when the execution reaches the printing line in run() method. At the same time when I try to run a thread that inherites my class it sees the value I set while doing previos creation.

I tried to run the following code:

public class InheritableThreadLocalExample {

  public static void main(String[] args) {
    ThreadOne threadOne = new ThreadOne("user-one", "thread-one");
    threadOne.start();
    ThreadTwo threadTwo = new ThreadTwo("user-two", "thread-two");
    threadTwo.start();
  }

  public static class ThreadOne extends Thread {

    protected static final InheritableThreadLocal<String> USERNAME = new InheritableThreadLocal<>();

    public ThreadOne(String username, String threadName) {
      super(threadName);
      USERNAME.set(username);
    }

    @Override
    public void run() {
      System.out.println(Thread.currentThread().getName() + ". Username: " + USERNAME.get());
    }
  }

  public static class ThreadTwo extends ThreadOne {
    public ThreadTwo(String username, String threadName) {
      super(username, threadName);
    }
  }

}

The output is:

thread-two. Username: user-one
thread-one. Username: null

Why is this happening?


Solution

  • The constructor of ThreadOne is executed by the main thread, therefore, the statement USERNAME.set(username); inside this constructor sets the value for the main thread. But the actual values of the inheritable thread locals of the main thread were copied in the constructor of Thread, i.e. the preceding super(threadName);, already.

    Therefore, the first thread inherits the state before the first USERNAME.set(username); call whereas the second thread inherits the state after that call; the inheritance relationship is irrelevant, as we can demonstrate:

    public class InheritableThreadLocalExample {
      public static void main(String[] args) {
        ThreadOne threadOne = new ThreadOne("user-one", "thread-one");
        printValue();
        threadOne.start();
        Thread threadTwo = new Thread(
                           InheritableThreadLocalExample::printValue, "thread-two");
         // things happening after Thread's constructor are irrelevant:
        ThreadOne.USERNAME.set("user-two");
        threadTwo.start();
      }
    
      static void printValue() {
        System.out.println(Thread.currentThread().getName()
                         + ". Username: " + ThreadOne.USERNAME.get());
      }
    
      public static class ThreadOne extends Thread {
        protected static final InheritableThreadLocal<String> USERNAME
                                      = new InheritableThreadLocal<>();
    
        public ThreadOne(String username, String threadName) {
          super(threadName);
          USERNAME.set(username);
        }
    
        @Override
        public void run() {
          printValue();
        }
      }
    }
    
    main. Username: user-one
    thread-one. Username: null
    thread-two. Username: user-one
    

    We can easily set the intended values in the main thread without subclasses:

    public class InheritableThreadLocalExample {
      static final InheritableThreadLocal<String> USERNAME
                                                = new InheritableThreadLocal<>();
    
      public static void main(String[] args) {
        USERNAME.set("user-one");
        Thread threadOne = new Thread(
            InheritableThreadLocalExample::printValue, "thread-one");
        USERNAME.set("user-two");
        Thread threadTwo = new Thread(
            InheritableThreadLocalExample::printValue, "thread-two");
    
        // demonstrate that each thread has its own value
        // and main doesn't need a value:
        USERNAME.remove();
    
        threadOne.start();
        threadTwo.start();
      }
    
      static void printValue() {
        System.out.println(Thread.currentThread().getName()
                         + ". Username: " + USERNAME.get());
      }
    }
    
    thread-one. Username: user-one
    thread-two. Username: user-two