摘要:ThreadLocal是除了加鎖同步方式之外的一種保證規避多線程訪問出現線程不安全的方法。
本文分享自華為雲社區《4問搞定java中的ThreadLocal》,作者:breakDraw。
多線程訪問同一個共享變量的時候容易出現併發問題,特別是多個線程對一個變量進行寫入的時候,為了保證線程安全,一般使用者在訪問共享變量的時候需要進行額外的同步措施才能保證線程安全性。ThreadLocal是除了加鎖這種同步方式之外的一種保證一種規避多線程訪問出現線程不安全的方法,當我們在創建一個變量後,如果每個線程對其進行訪問的時候訪問的都是線程自己的變量這樣就不會存在線程不安全問題。
Q: ThreadLocal的常見使用場景?
A:每個線程中需要維護1個不同的副本, 但這個副本可能是某一個時刻一起塞入每個線程的, 只不過之後該副本的變化 不再受其他線程的影響。
常見場景有連接器管理模塊connectorManager, 每個線程持有的connect變量是單獨使用的,不會互相影響或者需要加鎖。原因就是將其作為副本放入每個線程,當線程啓動連接或者關閉時,不影響其他線程裏的getConnect方法。
Q: ThreadLocal和Synchronized關鍵字的區別?
A:
Synchronized是用時間的消耗,來換取數據同步以及互不衝突
ThreadLocal則是用空間的消耗,來換取數據之間互不衝突(不涉及同步)
Q:TheadLocal在每個線程中是以什麼形式存儲的? 原理是什麼
A:這篇文章講解ThreadLocal源碼講解的蠻好的:
Java併發編程:深入剖析
看完後用我自己的話總結一下就是:
- 在某個線程中調用 某threadlocal.set(value)時, 其實就是在該線程中新建了1個threalocalMap, 然後把threadLocal作為鍵,value作為值,放進本線程的threalocalMap中。
- 當在線程中調用threadlocal.get()的時候,就是從線程的threadLocalMap中獲取這個threadLocal對應的值
如果get不到,則可以通過自定義initValue方法生成一個threadLocal的默認值
見如下圖所示:
Q: 下面這個代碼會報什麼錯?(例子改編自上面鏈接的文章)
public class Test {
ThreadLocal<String> stringLocal = new ThreadLocal<String>();
public static void main(String[] args) throws InterruptedException {
final Test test = new Test();
System.out.println(test.getString());
Thread thread1 = new Thread(){
public void run() {
System.out.println(stringLocal.get());
};
};
thread1.start();
thread1.join();
stringLocal.set("thread0")
System.out.println(test.getString());
}
}
在Thread1中,會報空指針, 因為調用get之前沒有做過set, 此時做get會報錯。
一種方式改成這樣:
Thread thread1 = new Thread(){
public void run() {
stringLocal.set("thread1")
System.out.println(stringLocal.get());
};
};
另一種是給stringLocal設置默認值,這種一般用於能直接根據線程推導出初始值的情況:
ThreadLocal<String> stringLocal = new ThreadLocal<String>(){;
protected String initialValue() {
return xxx;
};
};
正確set之後, 答案就會返回thread0和thread1, 且後續怎麼set,兩邊都不會互相影響各自的threadLocal,雖然看起來是都用的是同一個Test裏的成員。
點擊關注,第一時間瞭解華為雲新鮮技術~