ThreadLocal 的使用
1. 前言
本節內容主要是對 ThreadLocal 進行深入的講解,具體內容點如下:
- 了解 ThreadLocal 的誕生,以及總體概括,是學習本節知識的基礎;
- 了解 ThreadLocal 的作用,從整體層面理解 ThreadLocal 的程序作用,為本節的次重點;
- 掌握 ThreadLocal set 方法的使用,為本節重點內容;
- 掌握 ThreadLocal get 方法的使用,為本節重點內容;
- 掌握 ThreadLocal remove 方法的使用,為本節重點內容;
- 掌握多線程下的 ThreadLocal 的使用,為本節內容的核心。
2. ThreadLocal 概述
誕生:早在 JDK 1.2 的版本中就提供 java.lang.ThreadLocal,ThreadLocal 為解決多線程程序的并發問題提供了一種新的思路。使用這個工具類可以很簡潔地編寫出優美的多線程程序。
概述:ThreadLocal 很容易讓人望文生義,想當然地認為是一個 “本地線程”。其實,ThreadLocal 并不是一個 Thread,而是 Thread 的局部變量,也許把它命名為 ThreadLocalVariable 更容易讓人理解一些。
當使用 ThreadLocal 維護變量時,ThreadLocal 為每個使用該變量的線程提供獨立的變量副本,所以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應的副本。
總體概括:從線程的角度看,目標變量就象是線程的本地變量,這也是類名中 “Local” 所要表達的意思。
了解完 ThreadLocal 的總體介紹后,對其有了一個總體的了解,那我們接下來繼續探究 ThreadLocal 的真實面貌以及使用。
3. ThreadLocal 的作用
作用:ThreadLocal 是 JDK 包提供的,它提供了線程本地變量,也就是如果你創建了一個 ThreadLocal 變量,那么訪問這個變量的每個線程都會有這個變量的一個本地副本。當多個線程操作這個變量時,實際操作的是自己本地內存里面的變量,從而避免了線程安全問題。
ThreadLocal 是線程本地存儲,在每個線程中都創建了一個 ThreadLocalMap 對象,每個線程可以訪問自己內部 ThreadLocalMap 對象內的 value。通過這種方式,避免資源在多線程間共享。
使用場景:如為每個線程分配一個 JDBC 連接 Connection。這樣就可以保證每個線程的都在各自的 Connection 上進行數據庫的操作,不會出現 A 線程關了 B 線程正在使用的 Connection。還有 Session 管理等問題。
4. ThreadLocal set 方法
方法介紹:set 方法是為了設置 ThreadLocal 變量,設置成功后,該變量只能夠被當前線程訪問,其他線程不可直接訪問操作改變量。
實例:
public class DemoTest{
public static void main(String[] args){
ThreadLocal<String> localVariable = new ThreadLocal<> () ;
localVariable.set("Hello World");
}
}
Tips:set 方法可以設置任何類型的值,無論是 String 類型 ,Integer 類型,Object 等類型,原因在于 set 方法的 JDK 源碼實現是基于泛型的實現,此處只是拿 String 類型進行的舉例。
實例:
public void set(T value) { // T value , 泛型實現,可以 set 任何對象類型
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
5. ThreadLocal get 方法
方法介紹:get 方法是為了獲取 ThreadLocal 變量的值,get 方法沒有任何入參,直接調用即可獲取。
實例:
public class DemoTest{
public static void main(String[] args){
ThreadLocal<String> localVariable = new ThreadLocal<> () ;
localVariable.set("Hello World");
System.out.println(localVariable.get());
}
}
結果驗證:
Hello World
探究:請看如下程序,并給出輸出結果
實例:
public class DemoTest{
public static void main(String[] args){
ThreadLocal<String> localVariable = new ThreadLocal<> () ;
localVariable.set("Hello World");
localVariable.set("World is beautiful");
System.out.println(localVariable.get());
System.out.println(localVariable.get());
}
}
探究解析:從程序中來看,我們進行了兩次 set 方法的使用。
第一次 set 的值為 Hello World ;第二次 set 的值為 World is beautiful。接下來我們進行了兩次打印輸出 get 方法,那么這兩次打印輸出的結果都會是 World is beautiful。 原因在于第二次 set 的值覆蓋了第一次 set 的值,所以只能 get 到 World is beautiful。
結果驗證:
World is beautiful
World is beautiful
總結:ThreadLocal 中只能設置一個變量值,因為多次 set 變量的值會覆蓋前一次 set 的值,我們之前提出過,ThreadLocal 其實是使用 ThreadLocalMap 進行的 value 存儲,那么多次設置會覆蓋之前的 value,這是 get 方法無需入參的原因,因為只有一個變量值。
6. ThreadLocal remove 方法
方法介紹:remove 方法是為了清除 ThreadLocal 變量,清除成功后,該 ThreadLocal 中沒有變量值。
實例:
public class DemoTest{
public static void main(String[] args){
ThreadLocal<String> localVariable = new ThreadLocal<> () ;
localVariable.set("Hello World");
System.out.println(localVariable.get());
localVariable.remove();
System.out.println(localVariable.get());
}
}
Tips:remove 方法同 get 方法一樣,是沒有任何入參的,因為 ThreadLocal 中只能存儲一個變量值,那么 remove 方法會直接清除這個變量值。
結果驗證:
Hello World
null
7. 多線程下的 ThreadLocal
對 ThreadLocal 的常用方法我們已經進行了詳細的講解,那么多線程下的 ThreadLocal 才是它存在的真實意義,那么問了更好的學習多線程下的 ThreadLocal,我們來進行場景的創建,通過場景進行代碼實驗,更好的體會并掌握 ThreadLocal 的使用。
場景設計:
- 創建一個全局的靜態 ThreadLocal 變量,存儲 String 類型變量;
- 創建兩個線程,分別為 threadOne 和 threadTwo;
- threadOne 進行 set 方法設置,設置完成后沉睡 5000 毫秒,蘇醒后進行 get 方法打??;
- threadTwo 進行 set 方法設置,設置完成后直接 get 方法打印,打印完成后調用 remove 方法,并打印 remove 方法調用完畢語句;
- 開啟線程 threadOne 和 threadTwo ;
- 執行程序,并觀察打印結果。
結果預期:在 threadOne 設置成功后進入了 5000 毫秒的休眠狀態,此時由于只有 threadTwo 調用了 remove 方法,不會影響 threadOne 的 get 方法打印,這體現了 ThreadLocal 變量的最顯著特性,線程私有操作。
實例:
public class DemoTest{
static ThreadLocal<String> local = new ThreadLocal<>();
public static void main(String[] args){
Thread threadOne = new Thread(new Runnable() {
@Override
public void run() {
local.set("threadOne's local value");
try {
Thread.sleep(5000); //沉睡5000 毫秒,確保 threadTwo 執行 remove 完成
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(local.get());
}
});
Thread threadTwo = new Thread(new Runnable() {
@Override
public void run() {
local.set("threadTwo's local value");
System.out.println(local.get());
local.remove();
System.out.println("local 變量執行 remove 操作完畢。");
}
});
threadTwo. start();
threadOne. start();
}
}
結果驗證:
threadTwo's local value
local 變量執行 remove 操作完畢。
threadOne's local value
從以上結果來看,在 threadTwo 執行完 remove 方法后,threadOne 仍然能夠成功打印,這更加證明了 ThreadLocal 的專屬特性,線程獨有數據,其他線程不可侵犯。
8. 小結
ThreadLocal 是解決線程安全問題一個很好的思路,它通過為每個線程提供一個獨立的變量副本解決了變量并發訪問的沖突問題。在很多情況下,ThreadLocal 比直接使用 synchronized 同步機制解決線程安全問題更簡單,更方便,且結果程序擁有更高的并發性。
本節的重中之重是掌握 ThreadLocal 的方法使用以及其特點,核心內容為多線程下的 ThreadLocal 的使用。