ThreadLocal是線程封閉的重要措施,Spring中DateTimeContextHolder和RequestContextHolder也有用到。
ThreadLocal的使用場景
場景1:對象隔離–線程需要一個獨享的對象(例如SimpleDateFormat)
線程獨享一個對象工具類,例如Random、DateFormat,通常出於線程安全、提高效率兩方面的考慮。
對於線程獨享對象場景,主要是重寫innitialValue()方法。
public class RrightWaySimpleDateFormater {
public static ExecutorService threadPool = Executors.newFixedThreadPool(10);
/**
* 定義ThreadLocal變量--JDK8實現形式
*/
private static final ThreadLocal<SimpleDateFormat> dateThreadSafe = ThreadLocal.withInitial(
() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
);
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 1000; i++) {
int finalI = i;
threadPool.submit(() -> {
System.out.println(new RrightWaySimpleDateFormater().date(finalI));
});
}
threadPool.shutdown();
}
public String date(int seconds) {
return dateThreadSafe.get().format(new Date(1000 * seconds));
}
}
JDK7以及之前的實現形式
private static final ThreadLocal<SimpleDateFormat> dateThreadSafe2 = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};
需要注意的是,ThreadLocal通常不適合線程池。
阿里java手冊:
【強制】SimpleDateFormat是線程不安全的類,一般不要定義爲static變量。如果定義爲static,必須加鎖,或使用DateUtils工具類。亦推薦上述處理。
JDK8推薦使用DateTimeFormatter代替SimpleDateFormat,Instant代替Date,LocalDateTime代替Calendar。
場景2:對象傳遞
線程需要保存全局變量,可以讓不同的方法直接使用,而不需要讓數據作爲參數層層傳遞。
ThreadLocal可以用於保存一些業務信息,例如用戶權限信息、用戶名、userId、手機號等等。這些業務信息在同一個線程中相同,不同線程中內容不同。強調的是同一個請求內(同一個線程內)不同方法間的共享。
Map也可以存儲上述業務信息。多線程同時工作時,需要保證線程安全。例如,採用靜態ConcurrentHashMap變量,將線程ID作爲key,業務數據作爲Value保存,可以做到線程間隔離,但仍有性能影響。
public class RightWayThreadLocalUse {
public static void main(String[] args) {
new ServiceImpl1().service(new UserInfo("lzp", "1234567890"));
}
}
/**
* 接收數據
*/
class ServiceImpl1 {
public void service(UserInfo userInfo) {
UserInfoHolder.holder.set(userInfo);
new ServiceImpl2().service();
}
}
/**
* 處理數據1
*/
class ServiceImpl2 {
public void service() {
System.out.println("客戶名:" + UserInfoHolder.holder.get().cltNam);
new ServiceImpl3().service();
}
}
/**
* 處理數據2
*/
class ServiceImpl3 {
public void service() {
System.out.println("客戶號:" + UserInfoHolder.holder.get().cltNbr);
// 此時使用完ThreadLocal,回收該ThreadLocal
UserInfoHolder.holder.remove();
}
}
class UserInfoHolder {
public static ThreadLocal<UserInfo> holder = new ThreadLocal<>();
}
class UserInfo {
String cltNam;
String cltNbr;
public UserInfo(String cltNam, String cltNbr) {
this.cltNam = cltNam;
this.cltNbr = cltNbr;
}
}
ThreadLocal的優勢
(1)ThreadLocal變量線程私有,因此線程安全,不需要加鎖,從而沒有阻塞,提高了執行效率。
(2)高效利用內存,節省開銷。對於使用線程池的場景,只需要每個woker線程擁有一個該對象的ThreadLocal實例即可。
(3)對於調用鏈場景,避免對象的反覆傳遞,用於存儲業務信息,實現代碼解耦。