摘要: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里的成員。