併發-5-ThreadLocal

Coding挖掘機發表於2018-10-17

傳統的資料庫連線池管理程式碼如下:

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可能產生記憶體洩漏的問題

相關文章