ThreadLocal:线程保险箱,jdk官方定义
ThreadLocal说明.pngThread是线程私有的,每个线程都有其独立的副本变量,通过get/set方法设置/获取变量。
只要线程是活动的并且ThreadLocal实例是可访问的,则每个线程都对其线程局部变量的副本持有隐式引用。 线程消失后,其线程本地实例的所有副本都将进行垃圾回收(除非存在对这些副本的其他引用)。
Thread是线程私有的,每个线程都有其独立的副本变量,通过get/set方法设置/获取变量。
简单使用:
public class ThreadLocalTest {
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
threadLocal.set("thread-0");
System.out.println(Thread.currentThread().getName() + " " + threadLocal.get());
});
Thread t2 = new Thread(() -> {
threadLocal.set("thread-1");
System.out.println(Thread.currentThread().getName() + " " + threadLocal.get());
});
t1.start();
t2.start();
}
}
运行结果:
运行结果.png
可以看出每个线程的变量对其它线程都是不可见的,通过get方法只能获取自己线程通过set方法放入到ThreadLocal中的变量,如果没有调用过set方法,则获取的结果是null。也可以给ThreadLcoal设置一个初始值,并且ThreadLocal是一个泛型类。
public class ThreadLocalSimpleTest {
private static ThreadLocal<String> threadLocal = new ThreadLocal<String>() {
// 设置初始值
@Override
protected String initialValue() {
return "Alex";
}
};
public static void main(String[] args) {
System.out.println(threadLocal.get());
threadLocal.set("Blex");
System.out.println(threadLocal.get());
}
}
运行结果:
ThreadLocal设置初始值.png
第一次调用get方法得到的是初始值,第二次调用get方法获取的是set方法设置的值。
上下文设计模式
对于一个线程,可能有多个任务,分为多个执行步骤,如果第N个执行步骤需要用到第一个执行步骤中的一个变量,常见的方法是设置一个上下文Context,将这个变量放到上下文Context中,通过方法入参将Context传递下去,从而第N个步骤可以用到第一个步骤传过来的变量。但是如果方法过多,每个方法都需要加这样的以一个参数,是不合理的。此时就可以运用ThreadLocal来改善上下文设计模式。
具体的方案就是将上下文Context放到ThreadLocal中,从而在不用传递Context的前提下,也能访问到上下文Context,从而可以轻松对Context设置/获取里面的变量。
上下文Context定义:
public class Context {
private String name;
private String cardId;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCardId() {
return cardId;
}
public void setCardId(String cardId) {
this.cardId = cardId;
}
}
通过ThreadLocal包装Context:
public final class ActionContext {
private final static ThreadLocal<Context> threadLocal = new ThreadLocal<Context>() {
@Override
protected Context initialValue() {
// 在ThreadLocal中放入一个初始值
return new Context();
}
};
private ActionContext() {}
private static class ActionContextHolder {
final static ActionContext actionContext = new ActionContext();
}
public static ActionContext getActionContext() {
return ActionContextHolder.actionContext;
}
public static Context getContext() {
return threadLocal.get();
}
}
定义线程执行的单元:
public class ExecutionTask implements Runnable {
private QueryFromDBAction queryFromDBAction = new QueryFromDBAction();
private QueryFromHttpAction queryFromHttpAction = new QueryFromHttpAction();
@Override
public void run() {
// 步骤一:从DB中查询name
queryFromDBAction.execute();
System.out.println("The name query successful.");
// 步骤二:http方式查询cardId
queryFromHttpAction.execute();
System.out.println("The cardId query successful.");
// 省略其他步骤
// 获取上下文
Context context = ActionContext.getActionContext().getContext();
System.out.println("name:" + context.getName());
System.out.println("cardId:" + context.getCardId());
}
}
QueryFromDBAction:
public class QueryFromDBAction {
public void execute() {
try {
Thread.sleep(1000L);
String name = getName();
// 放入到线程上下文
ActionContext.getActionContext().getContext().setName(Thread.currentThread().getName() + "->" + name);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private String getName() {
return "HuaJian";
}
}
QueryFromHttpAction:
public class QueryFromHttpAction {
public void execute() {
try {
Thread.sleep(1000L);
String cardId = getCardId();
// // 放入到线程上下文
ActionContext.getActionContext().getContext().setCardId(Thread.currentThread().getName() + "->" + cardId);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private String getCardId() {
return "1223456789";
}
}
客户端创建5个线程运行:
public class ContextTest {
public static void main(String[] args) {
IntStream.range(1, 5)
.forEach(
i -> new Thread(new ExecutionTask()).start()
);
}
}
测试结果:
测试结果.png
可以看出,5个线程通过get方法取到的上下文都是不一样的,说明ThreadLocal是线程私有的。
网友评论