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