ThreadLocal的設計
首先看看ThreadLocal的接口:
1
2
3
|
Object get() ; // 返回當前線程的線程局部變量副本 protected Object initialValue(); // 返回該線程局部變量的當前線程的初始值 void set(Object value); // 設置當前線程的線程局部變量副本的值 |
ThreadLocal有3個方法,其中值得注意的是initialValue(),該方法是一個protected的方法,顯然是為了子類重寫而特意實現的。該方法返回當前線程在該線程局部變量的初始值,這個方法是一個延遲調用方法,在一個線程第1次調用get()或者set(Object)時才執行,并且僅執行1次。ThreadLocal中的確實實現直接返回一個null:
protected Object initialValue() { return null; }
ThreadLocal是如何做到為每一個線程維護變量的副本的呢?其實實現的思路很簡單,在ThreadLocal類中有一個Map,用于存儲每一個線程的變量的副本。
比如下面的示例實現:就相當于存Map 這個是ThreadLocal的get方法的實現
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public T get() { Thread t = Thread.currentThread(); //獲取當前線程 ThreadLocalMap map = getMap(t); if (map != null ) { ThreadLocalMap.Entry e = map.getEntry( this ); if (e != null ) return (T)e.value; } return setInitialValue(); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; } |
ThreadLocal與其它同步機制的比較
ThreadLocal和其它同步機制相比有什么優勢呢?ThreadLocal和其它所有的同步機制都是為了解決多線程中的對同一變量的訪問沖突,在普通的同步機制中,是通過對象加鎖來實現多個線程對同一變量的安全訪問的。這時該變量是多個線程共享的,使用這種同步機制需要很細致地分析在什么時候對變量進行讀寫,什么時候需要鎖定某個對象,什么時候釋放該對象的鎖等等很多。所有這些都是因為多個線程共享了資源造成的。ThreadLocal就從另一個角度來解決多線程的并發訪問,ThreadLocal會為每一個線程維護一個和該線程綁定的變量的副本,從而隔離了多個線程的數據,每一個線程都擁有自己的變量副本,從而也就沒有必要對該變量進行同步了。ThreadLocal提供了線程安全的共享對象,在編寫多線程代碼時,可以把不安全的整個變量封裝進ThreadLocal,或者把該對象的特定于線程的狀態封裝進ThreadLocal。
由于ThreadLocal中可以持有任何類型的對象,所以使用ThreadLocal get當前線程的值是需要進行強制類型轉換。但隨著新的Java版本(1.5)將模版的引入,新的支持模版參數的ThreadLocal<T>類將從中受益。也可以減少強制類型轉換,并將一些錯誤檢查提前到了編譯期,將一定程度地簡化ThreadLocal的使用。
總結
當然ThreadLocal并不能替代同步機制,兩者面向的問題領域不同。同步機制是為了同步多個線程對相同資源的并發訪問,是為了多個線程之間進行通信的有效方式;而ThreadLocal是隔離多個線程的數據共享,從根本上就不在多個線程之間共享資源(變量),這樣當然不需要對多個線程進行同步了。所以,如果你需要進行多個線程之間進行通信, 則使用同步機制;如果需要隔離多個線程之間的共享沖突,可以使用ThreadLocal,這將極大地簡化你的程序,使程序更加易讀、簡潔。
ThreadLocal常見用途:
存放當前session用戶
存放一些context變量,比如webwork的ActionContext
存放session,比如Spring hibernate orm的session
例子:用 ThreadLocal 實現每線程 Singleton
線程局部變量常被用來描繪有狀態“單子”(Singleton) 或線程安全的共享對象,或者是通過把不安全的整個變量封裝進 ThreadLocal,或者是通過把對象的特定于線程的狀態封裝進 ThreadLocal。例如,在與數據庫有緊密聯系的應用程序中,程序的很多方法可能都需要訪問數據庫。在系統的每個方法中都包含一個 Connection 作為參數是不方便的 — 用“單子”來訪問連接可能是一個雖然更粗糙,但卻方便得多的技術。然而,多個線程不能安全地共享一個 JDBC Connection。如清單 3 所示,通過使用“單子”中的 ThreadLocal,我們就能讓我們的程序中的任何類容易地獲取每線程 Connection 的一個引用。這樣,我們可以認為 ThreadLocal 允許我們創建每線程單子。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
package org.heinrich.app.connection; import java.sql.Connection; public class ConnectionUtils { private final static ThreadLocal<Connection> threadLocal = new ThreadLocal<>(); public Connection getConnection(){ Connection connection = threadLocal.get(); if (connection == null ){ connection = new DBHelper().getConn(); threadLocal.set(connection); } return connection; } } package org.heinrich.app.connection; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; //數據庫連接 public class DBHelper { public static final String url = "jdbc:mysql://localhost:3306/fk_test" ; public static final String name = "com.mysql.jdbc.Driver" ; public static final String user = "root" ; public static final String password = "root" ; public Connection conn = null ; public Connection getConn() { try { Class.forName(name); // 指定連接類型 conn = DriverManager.getConnection(url, user, password); // 獲取連接 } catch (Exception e) { e.printStackTrace(); } return conn; } } |
簡單的實現Mysql連接線程安全的一種方式
理論上來說,ThreadLocal是的確是相對于每個線程,每個線程會有自己的ThreadLocal。但是上面已經講到,一般的應用服務器都會維護一套線程池。因此,不同用戶訪問,可能會接受到同樣的線程。因此,在做基于TheadLocal時,需要謹慎,避免出現ThreadLocal變量的緩存,導致其他線程訪問到本線程變量。
感謝閱讀,希望能幫助到大家,謝謝大家對本站的支持!
原文鏈接:https://my.oschina.net/heinrichchen/blog/607013