ThreadLocal簡介_動力節(jié)點Java學(xué)院整理
ThreadLocal,直譯為“線程本地”或“本地線程”,如果你真的這么認(rèn)為,那就錯了!其實,它就是一個容器,用于存放線程的局部變量,我認(rèn)為應(yīng)該叫做 ThreadLocalVariable(線程局部變量)才對,真不理解為什么當(dāng)初 Sun 公司的工程師這樣命名。
早在 JDK 1.2 的時代,java.lang.ThreadLocal 就誕生了,它是為了解決多線程并發(fā)問題而設(shè)計的,只不過設(shè)計得有些難用,所以至
今沒有得到廣泛使用。其實它還是挺有用的,不相信的話,我們一起來看看這個例子吧。
一個序列號生成器的程序,可能同時會有多個線程并發(fā)訪問它,要保證每個線程得到的序列號都是自增的,而不能相互干擾。
先定義一個接口:
public interface Sequence { int getNumber(); }
每次調(diào)用 getNumber() 方法可獲取一個序列號,下次再調(diào)用時,序列號會自增。
再做一個線程類:
public class ClientThread extends Thread { private Sequence sequence; public ClientThread(Sequence sequence) { this.sequence = sequence; } @Override public void run() { for (int i = 0; i < 3; i++) { System.out.println(Thread.currentThread().getName() + " => " + sequence.getNumber()); } } }
在線程中連續(xù)輸出三次線程名與其對應(yīng)的序列號。
我們先不用 ThreadLocal,來做一個實現(xiàn)類吧。
public class SequenceA implements Sequence { private static int number = 0; public int getNumber() { number = number + 1; return number; } public static void main(String[] args) { Sequence sequence = new SequenceA(); ClientThread thread1 = new ClientThread(sequence); ClientThread thread2 = new ClientThread(sequence); ClientThread thread3 = new ClientThread(sequence); thread1.start(); thread2.start(); thread3.start(); } }
序列號初始值是0,在 main() 方法中模擬了三個線程,運行后結(jié)果如下:
Thread-0 => 1
Thread-0 => 2
Thread-0 => 3
Thread-2 => 4
Thread-2 => 5
Thread-2 => 6
Thread-1 => 7
Thread-1 => 8
Thread-1 => 9
由于線程啟動順序是隨機的,所以并不是0、1、2這樣的順序,這個好理解。為什么當(dāng) Thread-0 輸出了1、2、3之后,而 Thread-2 卻輸出了4、5、6呢?線程之間竟然共享了 static 變量!這就是所謂的“非線程安全”問題了。
那么如何來保證“線程安全”呢?對應(yīng)于這個案例,就是說不同的線程可擁有自己的 static 變量,如何實現(xiàn)呢?下面看看另外一個實現(xiàn)吧。
public class SequenceB implements Sequence { private static ThreadLocal<Integer> numberContainer = new ThreadLocal<Integer>() { @Override protected Integer initialValue() { return 0; } }; public int getNumber() { numberContainer.set(numberContainer.get() + 1); return numberContainer.get(); } public static void main(String[] args) { Sequence sequence = new SequenceB(); ClientThread thread1 = new ClientThread(sequence); ClientThread thread2 = new ClientThread(sequence); ClientThread thread3 = new ClientThread(sequence); thread1.start(); thread2.start(); thread3.start(); } }
通過 ThreadLocal 封裝了一個 Integer 類型的 numberContainer 靜態(tài)成員變量,并且初始值是0。再看 getNumber() 方法,首先從 numberContainer 中 get 出當(dāng)前的值,加1,隨后 set 到 numberContainer 中,最后將 numberContainer 中 get 出當(dāng)前的值并返回。
是不是很惡心?但是很強大!確實稍微饒了一下,我們不妨把 ThreadLocal 看成是一個容器,這樣理解就簡單了。所以,這里故意用 Container 這個單詞作為后綴來命名 ThreadLocal 變量。
運行結(jié)果如何呢?看看吧。
Thread-0 => 1
Thread-0 => 2
Thread-0 => 3
Thread-2 => 1
Thread-2 => 2
Thread-2 => 3
Thread-1 => 1
Thread-1 => 2
Thread-1 => 3
每個線程相互獨立了,同樣是 static 變量,對于不同的線程而言,它沒有被共享,而是每個線程各一份,這樣也就保證了線程安全。 也就是說,TheadLocal 為每一個線程提供了一個獨立的副本!
搞清楚 ThreadLocal 的原理之后,有必要總結(jié)一下 ThreadLocal 的 API,其實很簡單。
1.public void set(T value):將值放入線程局部變量中
2.public T get():從線程局部變量中獲取值
3.public void remove():從線程局部變量中移除值(有助于 JVM 垃圾回收)
4.protected T initialValue():返回線程局部變量中的初始值(默認(rèn)為 null)
為什么 initialValue() 方法是 protected 的呢?就是為了提醒程序員們,這個方法是要你們來實現(xiàn)的,請給這個線程局部變量一個初始值吧。
了解了原理與這些 API,其實想想 ThreadLocal 里面不就是封裝了一個 Map 嗎?自己都可以寫一個 ThreadLocal 了,嘗試一下吧。
public class MyThreadLocal<T> { private Map<Thread, T> container = Collections.synchronizedMap(new HashMap<Thread, T>()); public void set(T value) { container.put(Thread.currentThread(), value); } public T get() { Thread thread = Thread.currentThread(); T value = container.get(thread); if (value == null && !container.containsKey(thread)) { value = initialValue(); container.put(thread, value); } return value; } public void remove() { container.remove(Thread.currentThread()); } protected T initialValue() { return null; } }
以上完全山寨了一個 ThreadLocal,其中中定義了一個同步 Map(為什么要這樣?請讀者自行思考),代碼應(yīng)該非常容易讀懂。
下面用這 MyThreadLocal 再來實現(xiàn)一把看看。
public class SequenceC implements Sequence { private static MyThreadLocal<Integer> numberContainer = new MyThreadLocal<Integer>() { @Override protected Integer initialValue() { return 0; } }; public int getNumber() { numberContainer.set(numberContainer.get() + 1); return numberContainer.get(); } public static void main(String[] args) { Sequence sequence = new SequenceC(); ClientThread thread1 = new ClientThread(sequence); ClientThread thread2 = new ClientThread(sequence); ClientThread thread3 = new ClientThread(sequence); thread1.start(); thread2.start(); thread3.start(); } }
以上代碼其實就是將 ThreadLocal 替換成了 MyThreadLocal,僅此而已,運行效果和之前的一樣,也是正確的。
其實 ThreadLocal 可以單獨成為一種設(shè)計模式,就看你怎么看了。
ThreadLocal 具體有哪些使用案例呢?通過 ThreadLocal 存放 JDBC Connection,以達到事務(wù)控制的能力。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
java8 stream 由一個list轉(zhuǎn)化成另一個list案例
這篇文章主要介紹了java8 stream 由一個list轉(zhuǎn)化成另一個list案例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-08-08在navicat中導(dǎo)入mysql數(shù)據(jù)庫詳細(xì)步驟(即.sql后綴的數(shù)據(jù)庫)
Navicat是MySQL非常好用的可視化管理工具,功能非常強大,能滿足我們?nèi)粘?shù)據(jù)庫開發(fā)的所有需求,下面這篇文章主要給大家介紹了關(guān)于如何在navicat中導(dǎo)入mysql數(shù)據(jù)庫(即.sql后綴的數(shù)據(jù)庫)的相關(guān)資料,需要的朋友可以參考下2023-04-04Java之a(chǎn)pi網(wǎng)關(guān)斷言及過濾器案例講解
這篇文章主要介紹了Java之a(chǎn)pi網(wǎng)關(guān)斷言及過濾器案例講解,本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08Java將Word文檔轉(zhuǎn)換為PDF文件的幾種常用方法總結(jié)
這篇文章主要介紹了Java將Word文檔轉(zhuǎn)換為PDF文件的四種常用方法,分別使用ApachePOI+iText、Aspose.Words?for?Java、Docx4j和JODConverter,這些庫各有優(yōu)點,但在使用時需要注意庫與Java環(huán)境的兼容性、安裝所需依賴、轉(zhuǎn)換速度和資源消耗,需要的朋友可以參考下2024-10-10Spring?Boot?Security認(rèn)證之Redis緩存用戶信息詳解
本文介紹了如何使用Spring Boot Security進行認(rèn)證,并通過Redis緩存用戶信息以提高系統(tǒng)性能,通過配置RedisUserDetailsManager,我們成功地將用戶信息存儲到了Redis中,并在Spring Security中進行了集成,需要的朋友可以參考下2024-01-01