1. 程式人生 > >二十一、JAVA多執行緒筆記:執行緒上下文設計模式(ThreadLocal)

二十一、JAVA多執行緒筆記:執行緒上下文設計模式(ThreadLocal)

        上下文是貫穿整個系統或階段生命週期的物件,其中包含了系統全域性的一些資訊,比如登入後的使用者資訊、賬號資訊,以及在程式每一個階段執行時的資料。

        設計時要考慮到全域性唯一性,還要考慮有些成員只能被初始化一次。比如配置資訊載入。以及在多執行緒環境下,上下文成員的執行緒安全性

 

責任鏈模式

執行緒上下文模式

 

ThreadLocal詳解

簡介:


        ThreadLocal類

顧名思義可以理解為執行緒本地變數。也就是說如果定義了一個ThreadLocal,每個執行緒往這個ThreadLocal中讀寫是執行緒隔離,互相之間不會影響的。它提供了一種將可變資料通過每個執行緒有自己的獨立副本從而實現執行緒封閉的機制。

實現思路


        Thread類有一個型別為ThreadLocal.ThreadLocalMap的例項變數threadLocals,也就是說每個執行緒有一個自己的ThreadLocalMap。ThreadLocalMap有自己的獨立實現,可以簡單地將它的key視作ThreadLocal,value為程式碼中放入的值(實際上key並不是ThreadLocal本身,而是它的一個弱引用)。每個執行緒在往某個ThreadLocal裡塞值的時候,都會往自己的ThreadLocalMap裡存,讀也是以某個ThreadLocal作為引用,在自己的map裡找對應的key,從而實現了執行緒隔離。

 

使用場景

        1. 在進行物件跨層訪問的時候,避免方法多次傳遞,打破層次間的約束。

        2.執行緒間資料隔離

        3.進行事物操作,用於儲存執行緒事物資訊。

ThreadLocal並不是解決多執行緒下共享資源的技術,一般情況下,每個執行緒的ThreadLocal儲存的都是一個全新的物件(通過new關鍵字建立),如果多個執行緒的ThreadLocal儲存了一個物件的引用,那麼其還是會面臨資源的競爭,資料不一致等併發問題

 

ThreadLocal方法詳解與原始碼分析

 

package com.zl.step21;

import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;

import static java.lang.Thread.currentThread;

public class ThreadLocalExample {
    public static void main(String[] args) {
        ThreadLocal<Integer> tlocal = new ThreadLocal<>();


        IntStream.range(0,10).forEach( i -> {
            new Thread(()->{

                try {
                    tlocal.set(i);
                    System.out.println(currentThread()+" set i  : " + tlocal.get());


                    TimeUnit.SECONDS.sleep(1);


                    System.out.println(currentThread()+" get i  : " + tlocal.get());

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }


            }).start();



        });

    }
}

 

 

10個執行緒之前互相併不影響,每個執行緒存入ThreadLocal之間的i完全不同彼此獨立。

 

1.initialValue()方法

initialValue方法為ThreadLocal要儲存的資料型別指定了一個初始化值,在ThreadLocal中預設返回null

程式碼示例:

protected T initialValue() {
    return null;
}
ThreadLocal<Object> threadLocal = new ThreadLocal<Object>(){

    @Override
    protected Object initialValue(){
        return  new Object() ;
    }

};

new  Thread(()-> {
    System.out.println(threadLocal.get());
}).start();


System.out.println(threadLocal.get());

 

結果:

Connected to the target VM, address: '127.0.0.1:53305', transport: 'socket'
Disconnected from the target VM, address: '127.0.0.1:53305', transport: 'socket'
[email protected]
[email protected]

Process finished with exit code 0
 

 

 

 

 

2.set(T t) 方法

set方法主要是為了ThreadLocal指定將要被儲存的資料,如果重寫了initialValue() 方法, 在不呼叫set(T t)方法的時候,資料的初始值是initialValue()方法的計算結果,示例程式碼:

/**
 * 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資料結構
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

 

/**
 * Create the map associated with a ThreadLocal. Overridden in
 * InheritableThreadLocal.
 *
 * @param t the current thread
 * @param firstValue value for the initial entry of the map
 */
void createMap(Thread t, T firstValue) {
    //  以當前threadLocal為key 存放的資料為value
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

 

**
 * Set the value associated with key.
 *
 * @param key the thread local object
 * @param value the value to be set
 */
private void set(ThreadLocal<?> key, Object value) {

    // We don't use a fast path as with get() because it is at
    // least as common to use set() to create new entries as
    // it is to replace existing ones, in which case, a fast
    // path would fail more often than not.

    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);

    // 在map的set方法中遍歷整個map的entry,如果發現ThreadLocal相同,直接替換
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();
        //ThreadLocal相同,直接替換 
        if (k == key) {
            e.value = value;
            return;
        }
        // 建立新的資料
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    // 清理操作,防止記憶體溢位
    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

 

3.get() 方法

 

get用於返回當前執行緒在ThreadLocal中的資料備份,當前執行緒的資料存放在一個稱為ThreadLocalMap的資料結構中

 

/**
 * 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
    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();
}
/**
 * Variant of set() to establish initialValue. Used instead
 * of set() in case user has overridden the set() method.
 *
 * @return the initial value
 */
private T setInitialValue() {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}
 

ThreadLocalMap

完全類似於HashMap的資料結構,僅僅用於存放執行緒存放在ThreadLocal中的資料備份,ThreadLocalMap的所有方法對外都不可見

ThreadLocalMap中用於儲存的事Entry,他是一個WeakReference(弱引用)型別的子類。為了在垃圾會受到時候,自動回收,防止記憶體溢位的情況出現。

 

/**
 * The entries in this hash map extend WeakReference, using
 * its main ref field as the key (which is always a
 * ThreadLocal object).  Note that null keys (i.e. entry.get()
 * == null) mean that the key is no longer referenced, so the
 * entry can be expunged from table.  Such entries are referred to
 * as "stale entries" in the code that follows.
 */
static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

 

 

ThreadLocal的記憶體洩漏問題分析

1.WeakReference(弱引用)在JVM觸發任意GC 都會導致Entry的回收。完全是由HashMap充當的。

2.在get資料時增加檢查,清除已經被垃圾回收期回收的Entry

3.在set資料時增加檢查,清除已經被垃圾回收期回收的Entry

 

 

ThreadLocal實現上下文

 

第一版:

package com.zl.step21;


public class ActionContext {
    private static final ThreadLocal<Context> context = ThreadLocal.withInitial(Context::new) ;

    public static Context get(){
        return context.get();
    }

    static class Context{
        private Configuration configuration ;
        private OtherResource otherResource ;


        public Configuration getConfiguration(){
            return configuration ;
        }

        public void setConfiguration(Configuration configuration){
          this.configuration = configuration ;
        }


        public OtherResource getOtherResource(){
            return otherResource ;
        }

        public void setOtherResource(OtherResource otherResource){
            this.otherResource = otherResource ;
        }


    }

}


class Configuration {}
class OtherResource {}

第二版:

package com.zl.step21;


public class ActionContext {
    private static final ThreadLocal<Configuration> configuration = ThreadLocal.withInitial(Configuration::new) ;

    private static final ThreadLocal<OtherResource> otherResource = ThreadLocal.withInitial(OtherResource::new) ;


    public Configuration getConfiguration(){
        return configuration.get() ;
    }

    public void setConfiguration(Configuration conf){
        configuration.set(conf) ;
    }


    public OtherResource getOtherResource(){
        return otherResource.get();
    }

    public void setOtherResource(OtherResource oResource){
        otherResource.set(oResource) ;
    }
    
}


class Configuration {}
class OtherResource {}

ThreadLocal將指定的變數和當前執行緒繫結,執行緒間彼此隔離,持有不同物件的例項,避免競爭。

 

ThreadLocal存在記憶體洩漏的問題。