23. ThreadLocal变量
大约 4 分钟
1. Java变量的作用域
1.1. Java类中变量的作用域
从Java类的角度来看,Java变量的作用域有如下几种:
import java.util.concurrent.TimeUnit;
public class VariableScopeDemo {
private static int classVariable; // 类变量,作用范围为JVM进程
private int instanceVariable; // 类实例变量,作用范围为类实例内部
public static void increaseClassVariable() {
log("classVariable++");
classVariable++;
}
public static int getClassVariable() {
return classVariable;
}
public void increaseInstanceVariable() {
instanceVariable++;
}
public int getInstanceVariable() {
return instanceVariable;
}
public int increaseBlockVariable() {
int blockVariable = 0; // 代码块级别的变量,只在代码块内可见
blockVariable++;
return blockVariable;
}
private static class Thread1 extends Thread {
@Override
public void run() {
VariableScopeDemo.increaseClassVariable(); // 修改了类级别的变量,在另外一个线程中可以看到修改后的值
}
}
private static class Thread2 extends Thread {
@Override
public void run() {
log("classVariable: " + VariableScopeDemo.getClassVariable());
}
}
private static void log(String msg) {
System.out.println(msg);
}
private static void quietlySleep() {
try {
TimeUnit.SECONDS.sleep(1);
} catch(InterruptedException e) {
// nothing to do
}
}
public static void main(String[] args) {
Thread1 thread1 = new Thread1();
Thread2 thread2 = new Thread2();
thread1.start();
quietlySleep(); // 确保thread1先执行
thread2.start();
VariableScopeDemo demo1 = new VariableScopeDemo();
VariableScopeDemo demo2 = new VariableScopeDemo();
demo1.increaseInstanceVariable(); // 不会更改demo2的值
int instance1Variable = demo1.getInstanceVariable(); // 可以获得本实例修改后的值
int instance2Variable = demo2.getInstanceVariable();
log("demo1 instanceVariable: " + instance1Variable);
log("demo2 instanceVariable: " + instance2Variable);
int instance1BlockVariable = demo1.increaseBlockVariable();
int instance2BlockVariable = demo2.increaseBlockVariable();
log("demo1 instance1BlockVariable: " + instance1BlockVariable);
log("demo2 instance2BlockVariable: " + instance2BlockVariable);
}
}
打印日志:
classVariable++
demo1 instanceVariable: 1
demo2 instanceVariable: 0
demo1 instance1BlockVariable: 1
demo2 instance2BlockVariable: 1
classVariable: 1
1.2. 应用中变量的作用域
这里的应用包括web应用和JAVA进程,在应用中Java变量的作用范围有:
1.JVM变量 - 在Java进程中共享,就是上面提到的类变量
2.Application变量 - 在整个web应用中共享,只要后台应用存在,则一直存在
3.Session变量 - 在session中共享,浏览器打开某个网站则存在,浏览器关闭该网站则消失,不同的登录用户Session不同
4.线程变量 - 在线程中共享,即今天将要介绍的ThreadLocal
2. ThreadLocal应用场景
ThreadLocal变量使得不需要通过参数在不同的Java类之间传递参数,这为打印trace信息提供了便利,可以把业务无关的参数都放到ThreadLocal变量中,例如TraceId、员工号等等,在打印trace信息时再从ThreadLocal中获取。
如下图所示,ThreadLocal变量可以在一次业务请求线程中的多个类中传递:
2.1. 参数传递例子
public class ThreadLocalParamDemo {
private static class Controller {
public static void createUser(String traceId, String user) {
log(String.format("[%s] Controller.createUser be called. user: [%s]", traceId, user));
Service.createUser(traceId, user);
}
}
private static class Service {
public static void createUser(String traceId, String user) {
log(String.format("[%s] Service.createUser be called. user: [%s]", traceId, user));
Dao.createUser(traceId, user);
}
}
private static class Dao {
public static void createUser(String traceId, String user) {
log(String.format("[%s] Dao.createUser be called. user: [%s]", traceId, user));
}
}
private static void log(String msg) {
System.out.println(msg);
}
public static void main(String[] args) {
String traceId = "10001";
String user = "Jack";
Controller.createUser(traceId, user);
traceId = "10002";
user = "Leo";
Controller.createUser(traceId, user);
}
}
打印日志:
[10001] Controller.createUser be called. user: [Jack]
[10001] Service.createUser be called. user: [Jack]
[10001] Dao.createUser be called. user: [Jack]
[10002] Controller.createUser be called. user: [Leo]
[10002] Service.createUser be called. user: [Leo]
[10002] Dao.createUser be called. user: [Leo]
2.2. ThreadLocal例子
public class ThreadLocalDemo {
private static long traceId = 10000;
private static ThreadLocal<String> traceIdThreadLocal = new ThreadLocal<>();
private static class Controller {
public static void createUser(String user) {
log(String.format("[%s] Controller.createUser be called. user: [%s]", traceIdThreadLocal.get(), user));
Service.createUser(user);
}
}
private static class Service {
public static void createUser(String user) {
log(String.format("[%s] Service.createUser be called. user: [%s]", traceIdThreadLocal.get(), user));
Dao.createUser(user);
}
}
private static class Dao {
public static void createUser(String user) {
log(String.format("[%s] Dao.createUser be called. user: [%s]", traceIdThreadLocal.get(), user));
}
}
private static void log(String msg) {
System.out.println(msg);
}
private static void nextTraceId() {
traceId++;
traceIdThreadLocal.set(String.valueOf(traceId));
}
private static class CreateUserThread extends Thread {
private String user;
public CreateUserThread(String user) {
this.user = user;
}
@Override
public void run() {
nextTraceId(); //线程内设置,只有当前线程可见
Controller.createUser(user);
}
}
public static void main(String[] args) {
CreateUserThread createUserThread1 = new CreateUserThread("Jack");
createUserThread1.start();
CreateUserThread createUserThread2 = new CreateUserThread("Leo");
createUserThread2.start();
}
}
打印日志:
[10002] Controller.createUser be called. user: [Leo]
[10001] Controller.createUser be called. user: [Jack]
[10002] Service.createUser be called. user: [Leo]
[10001] Service.createUser be called. user: [Jack]
[10002] Dao.createUser be called. user: [Leo]
[10001] Dao.createUser be called. user: [Jack]
2.3. 父子线程是否共享ThreadLocal变量
public class ThreadLocalChildDemo {
private static ThreadLocal<String> traceIdThreadLocal = new ThreadLocal<>();
private static class ChildThread extends Thread {
@Override
public void run() {
System.out.println(traceIdThreadLocal.get());
}
}
public static void main(String[] args) {
traceIdThreadLocal.set("10001");
ChildThread childThread = new ChildThread();
childThread.start();
}
}
打印日志:
csharp
复制代码null
说明ThreadLocal变量只在同一个线程内共享。
3. 总结ThreadLocal的优点
1.线程安全,ThreadLocal是线程级变量,不会跨线程访问,因此线程安全。
2.分离关注点,将业务不需要关注的参数独立出来,业务代码中只需要统一调用通用的trace方法则可。
3.通过ThreadLocal在线程内传递变量,不会因为方法参数变化而修改业务方法,保持方法稳定。