1. 程式人生 > >高併發第六彈:執行緒封閉

高併發第六彈:執行緒封閉

當訪問共享的可變資料時,通常需要使用同步。一種避免使用同步的方式就是不共享資料。如果僅在單執行緒內訪問資料,就不需要同步。這種技術被稱為執行緒封閉。

它其實就是把物件封裝到一個執行緒裡,只有一個執行緒能看到這個物件,那麼這個物件就算不是執行緒安全的,也不會出現任何執行緒安全方面的問題。

二 執行緒封閉技術有一個常見的應用:

資料庫連線對應jdbc的Connection物件,Connection物件在實現的時候並沒有對執行緒安全做太多的處理,jdbc的規範裡也沒有要求Connection物件必須是執行緒安全的。 實際在伺服器應用程式中,執行緒從連線池獲取了一個Connection物件,使用完再把Connection物件返回給連線池,由於大多數請求都是由單執行緒採用同步的方式來處理的,並且在Connection物件返回之前,連線池不會將它分配給其他執行緒。因此這種連線管理模式處理請求時隱含的將Connection物件封閉線上程裡面,這樣我們使用的connection物件雖然本身不是執行緒安全的,但是它通過執行緒封閉也做到了執行緒安全。

實際實現的方法也就3種

1. ad-hoc(點對點)執行緒封閉

這是完全靠實現者控制的執行緒封閉,他的執行緒封閉完全靠實現者實現。Ad-hoc執行緒封閉非常脆弱,沒有任何一種語言特效能將物件封閉到目標執行緒上。

2 .棧封閉

棧封閉是我們程式設計當中遇到的最多的執行緒封閉。什麼是棧封閉呢?簡單的說就是區域性變數。多個執行緒訪問一個方法,此方法中的區域性變數都會被拷貝一分兒到執行緒棧中。所以區域性變數是不被多個執行緒所共享的,也就不會出現併發問題。所以能用區域性變數就別用全域性的變數,全域性變數容易引起併發問題

public class StackLocalDemo {

    
    
public int returnNum(int num) { // 物件被封閉在方法中,不要使它們逸出 List<Integer>demoList =new ArrayList<>(); for (int i = 0; i < num; i++) { demoList.add(i); } return demoList.size(); } }

3 . ThreadLocal執行緒封閉: 

它是一個特別好的封閉方法,其實ThreadLocal內部維護了一個map,map的key是每個執行緒的名稱,而map的value就是我們要封閉的物件。ThreadLocal提供了get、set、remove方法,每個操作都是基於當前執行緒的,所以它是執行緒安全的。 

/**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

//get方法都與當前執行緒繫結
/**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

其實也就是 是一個引數有了巨大的傳遞性(這其實也是一個大坑). 只要是同一個執行緒在同一個ThreadLocal裡面存放了資料,在其他任何地方都能取出來.

例如: 生命週期與執行緒生命週期相同:

  因此,ThreadLocal的一個非常大的“坑”就是當使用不當時,導致使用者不知道它的作用域範圍。大家可能覺得執行緒結束後ThreadLocal應該就回收了。假設執行緒真的登出了確實是這種,可是事實有可能並不是如此。比如線上程池中對執行緒管理都是採用執行緒複用的方法(Web容器通常也會採用執行緒池)。線上程池中執行緒非常難結束甚至於永遠不會結束。這將意味著執行緒持續的時間將不可預測,甚至與JVM的生命週期一致。

ThreadLocal使用的一般步驟:

1、在多執行緒的類(如ThreadDemo類)中。建立一個ThreadLocal物件threadXxx,用來儲存執行緒間須要隔離處理的物件xxx。
2、在ThreadDemo類中。建立一個獲取要隔離訪問的資料的方法getXxx(),在方法中推斷,若ThreadLocal物件為null時候,應該new()一個隔離訪問型別的物件,並強制轉換為要應用的型別。
3、在ThreadDemo類的run()方法中。通過getXxx()方法獲取要操作的資料。這樣能夠保證每一個執行緒相應一個數據物件,在不論什麼時刻都操作的是這個物件。

與Synchonized的對照:

ThreadLocal和Synchonized都用於解決多執行緒併發訪問。可是ThreadLocal與synchronized有本質的差別。synchronized是利用鎖的機制,使變數或程式碼塊在某一時該僅僅能被一個執行緒訪問。而ThreadLocal為每個執行緒都提供了變數的副本,使得每個執行緒在某一時間訪問到的並非同一個物件,這樣就隔離了多個執行緒對資料的資料共享。而Synchronized卻正好相反,它用於在多個執行緒間通訊時可以獲得資料共享。
 Synchronized用於執行緒間的資料共享,而ThreadLocal則用於執行緒間的資料隔離。