Why should Java ThreadLocal variables be static

JavaMultithreading

Java Problem Overview


I was reading the JavaDoc for Threadlocal here

<https://docs.oracle.com/javase/1.5.0/docs/api/java/lang/ThreadLocal.html>

and it says "ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID). "

But my question is why did they choose to make it static (typically) - it makes things a bit confusing to have "per thread" state but the fields are static?

Java Solutions


Solution 1 - Java

Because if it were an instance level field, then it would actually be "Per Thread - Per Instance", not just a guaranteed "Per Thread." That isn't normally the semantic you're looking for.

Usually it's holding something like objects that are scoped to a User Conversation, Web Request, etc. You don't want them also sub-scoped to the instance of the class.
One web request => one Persistence session.
Not one web request => one persistence session per object.

Solution 2 - Java

Either make it static or if you are trying to avoid any static fields in your class - make the class itself a singleton and then you can safely use the an instance level ThreadLocal as long as you have that singleton available globally.

Solution 3 - Java

It doesn't have to be. The important thing is that it should be a singleton.

Solution 4 - Java

The reason is that the variables are accessed via a pointer associated with the thread. They act like global variables with thread scope, hence static is the closest fit. This is the way that you get thread local state in things like pthreads so this might just be an accident of history and implementation.

Solution 5 - Java

Refer to this, this give better understanding.

In short, ThreadLocal object work like a key-value map. When the thread invoke ThreadLocal get/set method, it will retrieve/store the thread object in the map's key, and the value in the map's value. That's why different threads has different copied of value (that you want to store locally), because it resides in different map's entry.

That's why you only need one map to keep all values. Although not necessary, you can have multiple map (without declare static) to keep each thread object as well, which, it is totally redundant, that's why static variable is preferred.

Solution 6 - Java

A use for an threadlocal on a per instance per thread is if you want something to be visible in all methods of an object and have it thread safe without synchronizing access to it like you would so for an ordinary field.

Solution 7 - Java

static final ThreadLocal variables are thread safe.

static makes the ThreadLocal variable available across multiple classes for the respective thread only. it's a kind of Global variable decaration of the respective thread local variables across multiple classes.

We can check the this thread safety with the following code sample.

  • CurrentUser - stores the current user id in ThreadLocal
  • TestService - Simple service with method - getUser() to fetch the current user from CurrentUser.
  • TestThread - this class used for creating multiple threads and set userids concurrently

.

public class CurrentUser

public class CurrentUser {
private static final ThreadLocal<String> CURRENT = new ThreadLocal<String>();

public static ThreadLocal<String> getCurrent() {
	return CURRENT;
}

public static void setCurrent(String user) {
	CURRENT.set(user);
}

}

public class TestService {
			
public String getUser() {
	return CurrentUser.getCurrent().get();
}

}

.

import java.util.ArrayList;
import java.util.List;

public class TestThread {

public static void main(String[] args) {

  List<Integer> integerList = new ArrayList<>();

  //creates a List of 100 integers
  for (int i = 0; i < 100; i++) {

    integerList.add(i);
  }

  //parallel stream to test concurrent thread execution
  integerList.parallelStream().forEach(intValue -> {

    //All concurrent thread will set the user as "intValue"
    CurrentUser.setCurrent("" + intValue);
    //Thread creates a sample instance for TestService class
    TestService testService = new TestService();
    //Print the respective thread name along with "intValue" value and current user. 
    System.out.println("Start-"+Thread.currentThread().getName()+"->"+intValue + "->" + testService.getUser());

    try {
      //all concurrent thread will wait for 3 seconds
      Thread.sleep(3000l);
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    
    //Print the respective thread name along with "intValue" value and current user.
    System.out.println("End-"+Thread.currentThread().getName()+"->"+intValue + "->" + testService.getUser());
  });

}

}

.

Run TestThread main class. Output -

Start-main->62->62
Start-ForkJoinPool.commonPool-worker-2->31->31
Start-ForkJoinPool.commonPool-worker-3->81->81
Start-ForkJoinPool.commonPool-worker-1->87->87
End-main->62->62
End-ForkJoinPool.commonPool-worker-1->87->87
End-ForkJoinPool.commonPool-worker-2->31->31
End-ForkJoinPool.commonPool-worker-3->81->81
Start-ForkJoinPool.commonPool-worker-2->32->32
Start-ForkJoinPool.commonPool-worker-3->82->82
Start-ForkJoinPool.commonPool-worker-1->88->88
Start-main->63->63
End-ForkJoinPool.commonPool-worker-1->88->88
End-main->63->63
...

Analysis summary

  1. "main" thread starts and set current user as "62", parallely "ForkJoinPool.commonPool-worker-2" thread starts and set current user as "31", parallely "ForkJoinPool.commonPool-worker-3" thread starts and set current user as "81", parallely "ForkJoinPool.commonPool-worker-1" thread starts and set current user as "87" Start-main->62->62 Start-ForkJoinPool.commonPool-worker-2->31->31 Start-ForkJoinPool.commonPool-worker-3->81->81 Start-ForkJoinPool.commonPool-worker-1->87->87
  2. All these above threads will sleep for 3 seconds
  3. main execution ends and print the current user as "62", parallely ForkJoinPool.commonPool-worker-1 execution ends and print the current user as "87", parallely ForkJoinPool.commonPool-worker-2 execution ends and print the current user as "31", parallely ForkJoinPool.commonPool-worker-3 execution ends and print the current user as "81"

Inference

Concurrent Threads are able to retrieve correct userids even if it has declared as "static final ThreadLocal"

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionkellyfjView Question on Stackoverflow
Solution 1 - JavaAffeView Answer on Stackoverflow
Solution 2 - JavaAdnan MemonView Answer on Stackoverflow
Solution 3 - JavairreputableView Answer on Stackoverflow
Solution 4 - JavaUkkoView Answer on Stackoverflow
Solution 5 - JavaSam YCView Answer on Stackoverflow
Solution 6 - JavaChris MawataView Answer on Stackoverflow
Solution 7 - Javashijin rajView Answer on Stackoverflow