为什么 JavaThreadLocal 变量应该是静态的

我正在阅读 Threadlocal 的 JavaDoc

Https://docs.oracle.com/javase/1.5.0/docs/api/java/lang/threadlocal.html

上面写着 “ ThreadLocal 实例通常是类中的私有静态字段,这些类希望将状态与线程关联(例如,用户 ID 或事务 ID)。”

但我的问题是,为什么他们选择使其静态(通常)-它使事情有点混乱,有“每线程”的状态,但字段是静态的?

38099 次浏览

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.

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.

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

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.

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.

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.

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"