談一談不常見卻又不可少的ThreadLocal

談一談不常見卻又不可少的ThreadLocal

在寫ThreadLocal以前,須要先鞏固下一點相關知識:Java內存模型及共享變量的可見性。java

內存模型中全部變量存儲在主內存中,當一個線程中要使用某個變量時,須要從主內存複製該變量到其線程內才能操做,此時線程中操做的是主內存變量的副本,操做完成後再刷回主內存。刷回的實質就是變量賦值數據結構

若是多個線程訪問同一個變量時,每一個線程都具備一個副本,操做完畢後都會刷回主內存,刷回時間存在前後,則賦值有前後,固然後者會覆蓋前者,這是形成可見性問題的次要緣由。dom

引入以上知識點後,再來講明ThreadLocal。一個線程想使用某個變量,因而從主內存複製該共享變量到線程內部中。使用完畢後想再下次再次使用該變量時,獲得的變量副本是上次使用的副本,而不是從主內存的變量再次複製過來的副本,而且不想讓其餘線程影響到該變量。這就是ThreadLocal的目的,其實現不是經過共享變量這種方式實現的,詳細內容下面介紹ide

目的很明確,可是身處JAVA內存模型中要遵循內存模型規範,下面看看JDK是如何即知足內存模型規範,又知足ThreadLocal目的。函數

知足內存模型

這點很簡單,就是你該怎麼樣還怎麼樣,仍然受你管轄,該複製就複製,該刷回就刷回,不可見仍是會形成不可見。工具

知足ThreadLocal目的

多個線程都能訪問的變量才叫共享變量,若是控制變量的訪問方式,使其餘線程線程不能訪問就能夠了。控制方式就是將線程與變量的一一對應,將該變量的訪問入口控制到只有該線程便可,JDK中的作法就是讓線程持有這個變量(綁定到線程自己)優化

線程能夠綁定變量,可是並不知道須要綁定多少個,因而將這個存儲功能仍是交給專門的數據結構—>Map。而且還專門設計了一個用來訪問這個Map的工具,這個工具就是ThreadLocal。而且這個Map的key爲ThreadLocal實例的引用地址,value存儲真正的變量。this

這樣設計就到達目的了。ThreadLocal構建時接收個泛型告訴你存儲的變量是一個對象類型。spa

 

ThreadLocal設計

核心Map設計

static class ThreadLocalMap {
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;

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

這裏Map的Entry被設計爲弱引用。線程

內存存儲圖示以下

弱引用WeakReference

WeakReference是Java語言規範中爲了區別直接的對象引用(程序中經過構造函數聲明出來的對象引用)而定義的另一種引用關係。WeakReference標誌性的特色是:reference實例不會影響到被應用對象的GC回收行爲(即只要對象被除WeakReference對象以外全部的對象解除引用後,該對象即可以被GC回收),只不過在被對象回收以後,reference實例想得到被應用的對象時程序會返回null。

這裏使用弱引用的緣由:ThreadLocal目的就是達到變量只能本身訪問別的線程不能訪問的目的,Map設計的key爲ThreadLocal實例的引用地址,value爲變量,當線程存在而ThreadLocal實例被回收時,Map中的value仍是存在的(緣由後續說明)導致該變量的可達性分析失敗,從而致使此階段的內存泄露。當設計爲弱引用後,若是key被銷燬了(強引用銷燬),那麼value就處於可被回收狀態,從而避免內存泄露。

臨時內存泄露圖示

Map的持有狀態

在Thread類定義中這樣定義ThreadLocalMap

public class Thread implements Runnable {
  ...
  ThreadLocal.ThreadLocalMap threadLocals = null;
  ...
}

也就是ThreadLocalMap是線程類Thread持有的對象,一個線程持有一個ThreadLocalMap,只有線程存在,則ThreadLocalMap必存在。這也是ThreadLocal對象銷燬後,value還存在的緣由,ThreadLocalMap還被Thread強引用。只有線程銷燬時,ThreadLocalMap纔會隨之銷燬。
 

ThreadLocal的使用

private static final ThreadLocal<Integer> threadLocal = new ThreadLocal<>();  // 接收泛型

 

threadLocal.set(1);  // 對應泛型

 

Integer var=threadLocal.get();

 

ThreadLocal底層原理

設置變量 Set

public void set(T value) {
    // 獲取當前線程
    Thread t = Thread.currentThread();
// 從當前線程中獲取一個ThreadLocalMap實例
    ThreadLocalMap map = getMap(t);
// map存在則放入key,value  key爲當前ThreadLocal對象引用
    if (map != null)
        map.set(this, value);
    // map不存在則構建,一樣放入key,value  
    else
        createMap(t, value);
}

 

ThreadLocalMap實例的獲取就是從Thread中獲取的,也就是上面的持有狀態,拿到以後就能夠向Map結構中存儲key,value了,前面也說過了,這裏key存在的ThreadLocal實例的引用地址,value存在的變量的引用地址。一個線程能夠存儲多個ThreadLocal實例。

getMap(Thread t)

    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

就是獲取當前線程持有的ThreadLocalMap

createMap(Thread t, T firstValue)

void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

給當前線程初始化ThreadLocalMap實例,並set初始值。key爲ThreadLocal實例的引用地址

獲取變量Get

public T get() {
    Thread t = Thread.currentThread();  //① 獲取當前線程t
    ThreadLocalMap map = getMap(t); //②從當前線程中獲取一個ThreadLocalMap
    if (map != null) { //③ ThreadLocalMap不爲null則從map中獲取value ;     
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();//④ThreadLocalMap爲null則調用setInitialValue() 返回—初始化的值
}

 

在最後一個return以前的代碼就是從當前線程的ThreadLocalMap中經過Map的get方法獲取變量引用的過程。這是Map的基本用用法。

存在如下狀況則須要獲取初始的默認值,這是一個對外開放的功能,就是能夠指定ThreadLocal對應的變量的初始默認值,默認爲null,能夠被重寫

  • ThreadLocalMap未實例化
  • ThreadLocalMap已實例化,可是尚未Set變量

 

設置默認的初始值

protected T initialValue() {
    return null;
}
private T setInitialValue() {
    T value = initialValue(); //① 初始化爲null
    Thread t = Thread.currentThread(); //② 獲取當前線程
    ThreadLocalMap map = getMap(t); //③ 從當前線程中獲取一個ThreadLocalMap
    if (map != null)
        map.set(this, value); //④ ThreadLocalMap不爲null,則存儲key=this,value=value
    else
        createMap(t, value);//⑤ ThreadLocalMap爲null,則爲當前線程建立一個ThreadLocalMap並初始化值
    return value;
}
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

 

代碼流程也很簡單,只是爲了功能優化了,進行的統一封裝

  • ThreadLocalMap的惰性實例化
  • 獲取ThreadLocalMap時須要Thread,而ThreadLocal只是訪問控制工具,因而須要打通Thread來獲取ThreadLocalMap

移除

public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        m.remove(this);
}

主動移除是個好的處理方式,在不使用變量時應該主動移除

到這裏ThreadLocal自己的功能已經介紹完了,能夠理解爲變量訪問工具,這個變量的訪問被控制到只能當前線程有權限訪問,其餘線程無權限。

 

代碼示例

 

簡單示例

package cn.tinyice.demo.thread;

import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
* ThreadLocalDemo
*
* @author Tinyice
*/
public class ThreadLocalDemo {

    private static final ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
    private static final Random random=new Random();

    /**
     * 內存模型示例
     */
    private int mod=0;

    public ThreadLocalDemo(int mod) {
        this.mod = mod;
    }

    public int getMod() {
        return mod;
    }

    public void add() {
        mod+=1;
        // 每一個線程的值是不同的
        threadLocal.set(get() + random.nextInt(10));
    }

    public Integer get() {
        Integer integer = threadLocal.get();
        return null == integer ? 0 : integer;
    }

    public static void main(String[] args) {
        ThreadLocalDemo threadLocalDemo = new ThreadLocalDemo(0);
        for (int i = 0; i < 1000; i++) {
            new ThreadLocalThread(threadLocalDemo).start();
        }
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("------------------------------------------------");
        System.out.println(String.format("threadId=%d threadLocal value=%d mod=%d", Thread.currentThread().getId(), threadLocalDemo.get(),threadLocalDemo.getMod()));
    }

}

class ThreadLocalThread extends Thread {

    private ThreadLocalDemo threadLocalDemo;

    public ThreadLocalThread( ThreadLocalDemo threadLocalDemo) {
        this.threadLocalDemo = threadLocalDemo;
    }

    @Override
    public void run() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        threadLocalDemo.add();
        System.out.println(String.format("threadId=%d threadLocal value=%d", Thread.currentThread().getId(), threadLocalDemo.get()));
    }
}

 

控制檯:

threadId=853 threadLocal value=3
threadId=848 threadLocal value=9
threadId=808 threadLocal value=9
threadId=121 threadLocal value=8
------------------------------------------------
threadId=1 threadLocal value=0 mod=999

Process finished with exit code 0

mod=999 是可見性問題形成,而value值都不同則說明不一樣線程的變量值不同。

Spring示例

public abstract class TransactionSynchronizationManager {
    private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class);
    private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal("Transactional resources");
    private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations = new NamedThreadLocal("Transaction synchronizations");
    private static final ThreadLocal<String> currentTransactionName = new NamedThreadLocal("Current transaction name");
    private static final ThreadLocal<Boolean> currentTransactionReadOnly = new NamedThreadLocal("Current transaction read-only status");
    private static final ThreadLocal<Integer> currentTransactionIsolationLevel = new NamedThreadLocal("Current transaction isolation level");
    private static final ThreadLocal<Boolean> actualTransactionActive = new NamedThreadLocal("Actual transaction active");
...
}
public final class DateTimeContextHolder {
	private static final ThreadLocal<DateTimeContext> dateTimeContextHolder = new NamedThreadLocal<>("DateTimeContext");
       ...
}

 

原文地址: 程序猿微錄:談一談不常見卻又不可少的ThreadLocal

相關文章
相關標籤/搜索