【ThreadLocal】ThreadLocal使用場景及示例

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)對於調用鏈場景,避免對象的反覆傳遞,用於存儲業務信息,實現代碼解耦。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章