ThreadLocal:線程保險箱辜御,jdk官方定義
Thread是線程私有的泰涂,每個線程都有其獨立的副本變量囚痴,通過get/set方法設(shè)置/獲取變量撬讽。
只要線程是活動的并且ThreadLocal實例是可訪問的赡茸,則每個線程都對其線程局部變量的副本持有隱式引用缎脾。 線程消失后,其線程本地實例的所有副本都將進(jìn)行垃圾回收(除非存在對這些副本的其他引用)占卧。
Thread是線程私有的遗菠,每個線程都有其獨立的副本變量联喘,通過get/set方法設(shè)置/獲取變量。
簡單使用:
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();
}
}
運行結(jié)果:
可以看出每個線程的變量對其它線程都是不可見的辙纬,通過get方法只能獲取自己線程通過set方法放入到ThreadLocal中的變量豁遭,如果沒有調(diào)用過set方法,則獲取的結(jié)果是null贺拣。也可以給ThreadLcoal設(shè)置一個初始值蓖谢,并且ThreadLocal是一個泛型類。
public class ThreadLocalSimpleTest {
private static ThreadLocal<String> threadLocal = new ThreadLocal<String>() {
// 設(shè)置初始值
@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());
}
}
運行結(jié)果:
第一次調(diào)用get方法得到的是初始值譬涡,第二次調(diào)用get方法獲取的是set方法設(shè)置的值闪幽。
上下文設(shè)計模式
對于一個線程,可能有多個任務(wù)涡匀,分為多個執(zhí)行步驟盯腌,如果第N個執(zhí)行步驟需要用到第一個執(zhí)行步驟中的一個變量,常見的方法是設(shè)置一個上下文Context渊跋,將這個變量放到上下文Context中腊嗡,通過方法入?yún)ontext傳遞下去,從而第N個步驟可以用到第一個步驟傳過來的變量拾酝。但是如果方法過多燕少,每個方法都需要加這樣的以一個參數(shù),是不合理的蒿囤。此時就可以運用ThreadLocal來改善上下文設(shè)計模式客们。
具體的方案就是將上下文Context放到ThreadLocal中,從而在不用傳遞Context的前提下材诽,也能訪問到上下文Context底挫,從而可以輕松對Context設(shè)置/獲取里面的變量。
上下文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();
}
}
定義線程執(zhí)行的單元:
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";
}
}
客戶端創(chuàng)建5個線程運行:
public class ContextTest {
public static void main(String[] args) {
IntStream.range(1, 5)
.forEach(
i -> new Thread(new ExecutionTask()).start()
);
}
}
測試結(jié)果:
可以看出脸侥,5個線程通過get方法取到的上下文都是不一樣的建邓,說明ThreadLocal是線程私有的。