1. 程式人生 > >java 1.8 ThreadLocal原始碼分析

java 1.8 ThreadLocal原始碼分析

1. 關於ThreadLocal

當使用ThreadLocal維護變數時,ThreadLocal為每個使用該變數的執行緒提供獨立的變數副本,所以每一個執行緒都可以獨立地改變自己的副本,而不會影響其它執行緒所對應的副本。從執行緒的角度看,目標變數就象是執行緒的本地變數,這也是類名中“Local”所要表達的意思。

建立執行緒區域性私有的變數,避免多執行緒之間的競爭,是一種簡單的多執行緒安全類。

        我對ThreadLocal的理解就是:某個資料是共享的,但是共享的粒度是執行緒內部,即該執行緒裡執行的所有程式碼,所有方法內獲取到的都是同一個值。
區別與其他的形式:如果不用ThreadLocal,而是使用static 變數,創建出的全域性共享的話,在多執行緒的環境下,需要同步(簡單的例子是多執行緒下的雙判定的單例模式),如資料庫連線管理類ConnectionManager,這個例子見下:

class ConnectionManager {

    private static Connection connect = null;

    public synchronized static Connection openConnection() {
        if(connect == null){
            connect = DriverManager.getConnection();
        }
        return connect;
    }

    public synchronized  static void closeConnection
() { if(connect!=null) connect.close(); } }

        一個簡單的同步是直接使用synchronized 方法,但是這種的實現使得每次只能有一個執行緒在操作ConnectionManager.但是這種情況下,併發度太小(就這個情景下,使用連線池效能更好,這裡只是使用這個說明做個ThreadLocal引入的例子),變成每個執行緒一個connectionmanager,這樣的話,就不用在connectionmanager類中使用同步方法了,增加了處理時間。
        那ThreadLocal和直接的將某個物件變成普通的區域性變數,有什麼不同呢?
如下面的程式碼:

class ConnectionManager {

    private  Connection connect = null;

    public Connection openConnection() {
        if(connect == null){
            connect = DriverManager.getConnection();
        }
        return connect;
    }

    public void closeConnection() {
        if(connect!=null)
            connect.close();
    }
}

        我對次的理解是這樣的,加入有個管理類Manager,裡面有個成員是普通的成員變數SomeType obj,這樣,每個Manager例項都有一個obj物件,這和上面需求的每個Thread一份obj還是不同的。而要做到每個Thread一份obj,要麼將obj物件放到Thread子類或相關的類中(如Runnable等),或是,每建立一個Thread,手動的繫結一個obj物件,不然,是沒有辦法做到每個Thread一份obj。但是這樣的話,使得一些獨立的功能和Thread耦合起來了,或是使得你需要提前的需要知道Thread個數,好讓你手動的建立obj物件,獨立的繫結上去。一般來說,我們更希望一些工具類,管理類能夠獨立出去,而不是和Thread,Runanble雜在一起,能夠自動的感知有多少的Thread,就建立多少的拷貝,這就是使用ThreadLocal的好處!!

2. 使用例子

class Manager{
    static ThreadLocal< String> s=new ThreadLocal<String>();;
}
public class Test implements Runnable{

        public static void main(String args[]) 
        {
            Test r1=new Test();         
            Thread s1=new Thread(r1);      
            Thread s2=new Thread(r1);       

            s1.start();
            s2.start();
        }
        @Override
        public void run() {
            if(Manager.s.get()==null)
                Manager.s.set(new String(Thread.currentThread().getName()));
            System.out.println(Manager.s.get());

            //use it do something else
        }

}

3. 方法分析

a.建構函式

    public ThreadLocal() {  }

b.get()

 public T get() 
    {
        Thread t = Thread.currentThread();
        //獲取與當前執行緒繫結的ThreadLocalMap物件
        //getMap(t) ->  return t.threadLocals;
        ThreadLocalMap map = getMap(t);
        if (map != null) 
        {
            //在hashmap中查詢以當前ThreadLocal為key物件的Entry
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        //因為ThreadLocaMap是懶載入的,即存在一個有效的條目時,才會創建出
        //與當前Thread相關的threadLocalMap物件,所以,在set呼叫前,呼叫了
        //get,會得到null的threadLocalMap物件
        return setInitialValue();
    }

    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;
    }

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

         Thread物件中成員變數:ThreadLocal.ThreadLocalMap threadLocals = null;

關於ThreadLocal.ThreadLocalMap類:簡而言之,就是一個map,裡面的每個條目Entry的key為WeakReference型別的物件,value為該變數的該執行緒中的實際值(即如果如果該執行緒中的ThreadLocal物件沒有其他物件引用了的話,該Entry就會被回收)

        /**
         * 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;
            }
        }
  //ThreadLocalMap是一個線性探測hash表,預設的初始容量為16,threshold=2/3,ThreadLocal為key,當新增entry時,使用ThreadLocal的hashcode來線上性表中查詢合適的位置
           private Entry getEntry(ThreadLocal<?> key) 
        {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }

set方法

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

//threadlocalmap的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);

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) 
            {
                ThreadLocal<?> k = e.get();

                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();
        }

4. 總結

  1. ThreadLocal有一個final型別的int變數(int threadLocalHashCode = nextHashCode()),表示該TreadLocal變數的hash值,該hash值有一個全域性的AtomicInteger來管理,即系統中ThreadLocal變數有同一個AtomicInteger變數自增而得,因為Thread數母隨機,基本能保證同一個Thread中的ThreadLocal變數hashcode不斷變化,使得能均勻的對映到ThreadLocalMap上。
  2. 每個執行緒Thread物件都有一個ThreadLocal.ThreadLocalMap物件,執行緒的ThreadLocal變數存放在該ThreadLocalMap中。
  3. ThreadLocal為懶載入的,即當使用到第一個ThreadLocal物件時,才會創建出執行緒的ThreadLocalMap物件,該map為執行緒探測線性map,初始的預設大小為16,threshold為2/3.
  4. ThreadLocalMap使用迴圈陣列存放兼值對ThreadLocalMap.Entry為WeakSoftReference子類,即當系統中不存在對ThreadLocal物件時,該Entry就會被回收(由於沒有建立該WeakSoftReference物件相關的 reference queue,所以,只保證在記憶體不充足的時候該Entry被回收)
  5. 在ThreadLocalMap中查詢時,首先使用ThreadLocal物件(key)的hashcode定位可能資料索引,然後依次遍歷,直到找到對應的Entry,或是遇到null的Entry