傳統的資料庫連線池管理程式碼如下:
public class ThreadLocalTest {
//按照這種方法管理資料庫連線,不會產生執行緒衝突問題,但是會消耗記憶體
public void insert() throws SQLException {
ConnectionManager connectionManager = new ConnectionManager();
Connection connection = connectionManager.getConnection();
//insert的業務邏輯
connection.close();
}
}
class ConnectionManager {
//按照這種方法管理資料庫連線的話,會產生執行緒衝突問題
private Connection connection = null;
public Connection getConnection() throws SQLException {
//這裡可能會產生同步問題,如果有兩個執行緒A,B同時進入到這一行程式碼,發現connection都為null
//執行緒A和執行緒B就會重複建立Connection
if (connection == null) {
connection = DriverManager.getConnection("xx");
}
return connection;
}
public void closeConnection() throws SQLException {
//如果執行緒A在使用connection進行訪問,執行緒2檢測到connection!=null,進行關閉
//這也是一種錯誤,應該對connection進行互斥訪問
if (connection != null) {
connection.close();
}
}
}
複製程式碼
由於每次都需要在進行資料業務邏輯時才進行資料庫連線建立,資料庫連線其實是儲存在方法棧中的,相互之間不影響,也就不存在了同步的問題。
ThreadLocal執行緒本地變數:
ThreadLocal為變數在每個執行緒中都建立了一個副本,每個執行緒內部都有自己的一個變數,且線上程的任何地方都可以使用,執行緒之間不會相互影響 首先,在每個執行緒Thread內部有一個ThreadLocal.ThreadLocalMap型別的成員變數threadLocals,這個threadLocals就是用來儲存實際的變數副本的,鍵值為當前ThreadLocal變數,value為變數副本(即T型別的變數)。
初始時,在Thread裡面,threadLocals為空,當通過ThreadLocal變數呼叫get()方法或者set()方法,就會對Thread類中的threadLocals進行初始化,並且以當前ThreadLocal變數為鍵值,以ThreadLocal要儲存的副本變數為value,存到threadLocals。
然後在當前執行緒裡面,如果要使用副本變數,就可以通過get方法在threadLocals裡面查詢。
使用案例:
對於每個htpp請求,我們需要跟蹤進入每個方法時呼叫的資源數量做計數器計數器,如果所有的請求共用一個計數器,那麼這個計數器是全部請求的呼叫資源的次數 所以需要使用ThreadLocal來對每個執行緒儲存對應的執行緒變數
public class ThreadLocalUse {
//每個ThreadLocal為一個Map型別的資料結構,key為當前執行緒,value為所需要放入的值
private static ThreadLocal<Long> longLocals = new ThreadLocal<>();
private static ThreadLocal<String> stringLocals = new ThreadLocal<>();
public void set() {
longLocals.set(Thread.currentThread().getId());
stringLocals.set(Thread.currentThread().getName());
}
public Long getLong() {
return longLocals.get();
}
public String getString() {
return stringLocals.get();
}
public static void main(String[] agrs) {
ThreadLocalUse localUse = new ThreadLocalUse();
localUse.set();
new Thread(() -> {
localUse.set();
System.out.println(localUse.getLong());
System.out.println(localUse.getString());
}).start();
System.out.println(localUse.getLong());
System.out.println(localUse.getString());
}
}
複製程式碼
輸出:
9
Thread-0
1
main
複製程式碼
注意事項:
0.ThreadLocal是用來維護本執行緒的變數的,並不能解決共享變數的併發問題。ThreadLocal是各執行緒將值存入該執行緒的map中,以ThreadLocal自身作為key,需要用時獲得的是該執行緒之前存入的值。如果存入的是共享變數,那取出的也是共享變數,併發問題還是存在的。
1.ThreadLocal不是用來解決共享物件的多執行緒訪問問題的,一般情況下,通過ThreadLocal.set()到執行緒中的物件應該是執行緒自己使用的物件,其他執行緒是不需要訪問的,也訪問不到的。每個執行緒中都有一個自己的ThreadLocalMap類物件,可以將執行緒自己的物件保持到其中,各管各的,執行緒可以正確的訪問到自己的物件。
2.將一個共用的ThreadLocal靜態例項作為key,將不同物件的引用儲存到不同執行緒的ThreadLocalMap中,然後線上程執行的各處通過這個靜態ThreadLocal例項的get()方法取得自己執行緒儲存的那個物件,避免了將這個物件作為引數傳遞的麻煩。
3.很多封裝的執行緒池(Executors)中的執行緒生命週期是很難預測的,這個時候如果使用ThreadLocal可能產生記憶體洩漏的問題