1. 程式人生 > >類ThreadLocal的使用與源碼分析

類ThreadLocal的使用與源碼分析

thread getname == 使用方法 table local print com some

  變量值的共享可以使用public static的形式,所有的線程都使用同一個變量。如果每個線程都有自己的共享變量,就可以使用ThreadLocal。比如Hibernat的session問題就是存在ThreadLoca中。

  類ThreadLocal主要解決的就是每個線程綁定自己的值,可以將ThreadLocal比喻成存放數據的盒子,盒子中可以存儲每個線程的私有數據。

  而且ThreadLocal一般用作靜態成員變量封裝在工具類中實現線程隔離數據。在JavaEE結構中就是從Action層到Dao層可以使用threadLocal實現共享數據,並且線程之間相互隔離。(對於ThreadLocal,每個線程存進去的東西與取出來的是一致的,不會出現相互覆蓋的現象。)

1. 方法 get()與null

  ThreadLocal的基本使用方法。

package cn.qlq.thread.ten;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * ThreadLocal的基本使用
 * 
 * @author QiaoLiQiang
 * @time 2018年12月15日下午9:00:19
 */
public class Demo1 {
    public static ThreadLocal<String> t1 = new ThreadLocal<String>();
    
private static final Logger LOGGER = LoggerFactory.getLogger(Demo1.class); public static void main(String[] args) { if (t1.get() == null) { LOGGER.info("從未放過值"); t1.set("存放的值"); } LOGGER.info("{}", t1.get()); LOGGER.info("{}", t1.get()); } }

結果:

21:02:38 [cn.qlq.thread.ten.Demo1]-[INFO] 從未放過值
21:02:38 [cn.qlq.thread.ten.Demo1]-[INFO] 存放的值
21:02:38 [cn.qlq.thread.ten.Demo1]-[INFO] 存放的值

  從第一個的返回結果看,第一次調用t1對象的get()方法時返回的值是null,通過set()賦值之後可以取出值。類ThreadLocal解決的是變量在不同線程間的隔離性,也就是每個線程擁有自己的值,不同線程中的值是可以放入ThreadLocal類中進行保存的。

  

2.驗證變量間的隔離性

  驗證其隔離性,每個線程在變量間存放的值不同。

package cn.qlq.thread.ten;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * ThreadLocal的基本使用
 * 
 * @author QiaoLiQiang
 * @time 2018年12月15日下午9:00:19
 */
public class Demo2 {
    public static ThreadLocal<String> t1 = new ThreadLocal<String>();
    private static final Logger LOGGER = LoggerFactory.getLogger(Demo2.class);

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                if (t1.get() == null) {
                    LOGGER.info("從未放過值,threadName->{}", Thread.currentThread().getName());
                    t1.set("存放的值" + Thread.currentThread().getName());
                }
                LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get());
            }
        }, "thread1").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                if (t1.get() == null) {
                    LOGGER.info("從未放過值,threadName->{}", Thread.currentThread().getName());
                    t1.set("存放的值" + Thread.currentThread().getName());
                }
                LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get());
            }
        }, "thread2").start();
    }
}

結果: (由結果可以看出每個線程存放了不同的值,但是在獲取值的時候,每個線程又獲取到了不同的值。)

21:11:08 [cn.qlq.thread.ten.Demo2]-[INFO] 從未放過值,threadName->thread2
21:11:08 [cn.qlq.thread.ten.Demo2]-[INFO] 從未放過值,threadName->thread1
21:11:08 [cn.qlq.thread.ten.Demo2]-[INFO] threadName - >thread2,值->存放的值thread2
21:11:08 [cn.qlq.thread.ten.Demo2]-[INFO] threadName - >thread1,值->存放的值thread1

3.解決get()返回null問題

  為了解決返回為null的問題,也就是在get()的時候直接就返回默認值,采用繼承ThreadLocal並且重寫initialValue的方式實現。(初始值的時候也可以實現線程的隔離線)

package cn.qlq.thread.ten;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 解決get()返回null的問題
 * 
 * @author QiaoLiQiang
 * @time 2018年12月15日下午9:16:17
 */
public class Demo3<T> extends ThreadLocal<String> {
    public static Demo3<String> t1 = new Demo3<String>();
    private static final Logger LOGGER = LoggerFactory.getLogger(Demo3.class);

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                if (t1.get() == null) {
                    LOGGER.info("從未放過值,threadName->{}", Thread.currentThread().getName());
                    t1.set("存放的值" + Thread.currentThread().getName());
                }
                LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get());
            }
        }, "thread1").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                if (t1.get() == null) {
                    LOGGER.info("從未放過值,threadName->{}", Thread.currentThread().getName());
                    t1.set("存放的值" + Thread.currentThread().getName());
                }
                LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get());
            }
        }, "thread2").start();
    }

    @Override
    protected String initialValue() {
        return "這是初始值" + Thread.currentThread().getName();
    }
}

結果:

21:23:54 [cn.qlq.thread.ten.Demo3]-[INFO] threadName - >thread2,值->這是初始值thread2
21:23:54 [cn.qlq.thread.ten.Demo3]-[INFO] threadName - >thread1,值->這是初始值thread1

4. ThreadLocal中存入多個對象

  有時候我們在ThreadLocal 中希望共享多個變量。

  最簡單的一種辦法創建一個ThreadLocal就是將所有共享的數據存入一個Map,將Map存入ThreadLocal,另一種辦法就是所有共享數據放入一個bean中將bean存入ThreadLocal。

  另一種辦法就是每個創建多個ThreadLocal分別存放多種共享的數據。

如下一個ThreadLocal存入Map中實現共享多個數據

package cn.qlq.thread.ten;

import java.util.HashMap;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@SuppressWarnings("all")
public class Demo7 {
    public static ThreadLocal t1 = new ThreadLocal();

    private static final Logger LOGGER = LoggerFactory.getLogger(Demo7.class);

    public static void main(String[] args) throws InterruptedException {
        Map data = new HashMap();
        data.put("str", "111222");
        data.put("int", 11122);
        data.put("obj", new Object());
        t1.set(data);
        Object object = t1.get();
        System.out.println(object);
    }
}

結果:

{str=111222, int=11122, obj=java.lang.Object@77700f3d}

或者多個ThreadLocal共享多個數據

package cn.qlq.thread.ten;

import java.util.HashMap;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@SuppressWarnings("all")
public class Demo7 {
    public static ThreadLocal t1 = new ThreadLocal();
    public static ThreadLocal<String> t2 = new ThreadLocal<String>();

    private static final Logger LOGGER = LoggerFactory.getLogger(Demo7.class);

    public static void main(String[] args) throws InterruptedException {
        Map data = new HashMap();
        data.put("str", "111222");
        data.put("int", 11122);
        data.put("obj", new Object());
        t1.set(data);

        t2.set("t2");

        System.out.println(t1.get());
        System.out.println(t2.get());
    }
}

結果:

{str=111222, int=11122, obj=java.lang.Object@271455a2}
t2

5. 類InheritableThreadLocal的使用

5.1值繼承

  使用InheritableThreadLocal可以在子線程從父線程中繼承值。主線程存入值,在子線程中獲取。

package cn.qlq.thread.ten;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 主線程中設置值,子線程中獲取值
 * 
 * @author QiaoLiQiang
 * @time 2018年12月15日下午9:29:40
 * @param <T>
 */
public class Demo4<T> extends InheritableThreadLocal<String> {
    public static Demo4<String> t1 = new Demo4<String>();
    private static final Logger LOGGER = LoggerFactory.getLogger(Demo4.class);

    public static void main(String[] args) {
        // 主線程中存入值
        t1.set("存放的值" + Thread.currentThread().getName());

        new Thread(new Runnable() {
            @Override
            public void run() {
                if (t1.get() == null) {
                    LOGGER.info("從未放過值,threadName->{}", Thread.currentThread().getName());
                    t1.set("存放的值" + Thread.currentThread().getName());
                }
                LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get());
            }
        }, "thread1").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                if (t1.get() == null) {
                    LOGGER.info("從未放過值,threadName->{}", Thread.currentThread().getName());
                    t1.set("存放的值" + Thread.currentThread().getName());
                }
                LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get());
            }
        }, "thread2").start();
    }
}

結果:

21:29:02 [cn.qlq.thread.ten.Demo4]-[INFO] threadName - >thread2,值->存放的值main
21:29:02 [cn.qlq.thread.ten.Demo4]-[INFO] threadName - >thread1,值->存放的值main

測試在子線程中再次創建子線程。(值會一直繼承下去,對自己的子線程創建的子線程也有效

package cn.qlq.thread.ten;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 主線程中設置值,子線程中獲取值
 * 
 * @author QiaoLiQiang
 * @time 2018年12月15日下午9:29:40
 * @param <T>
 */
public class Demo5<T> extends InheritableThreadLocal<String> {
    public static Demo5<String> t1 = new Demo5<String>();
    private static final Logger LOGGER = LoggerFactory.getLogger(Demo5.class);

    public static void main(String[] args) {
        // 主線程中存入值
        t1.set("存放的值" + Thread.currentThread().getName());

        // 創建子線程獲取值
        new Thread(new Runnable() {
            @Override
            public void run() {
                LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get());

                // 創建子子線程獲取值
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get());
                    }
                }, "thread2").start();
            }
        }, "thread1").start();
    }
}

結果:

21:32:34 [cn.qlq.thread.ten.Demo5]-[INFO] threadName - >thread1,值->存放的值main
21:32:34 [cn.qlq.thread.ten.Demo5]-[INFO] threadName - >thread2,值->存放的值main

5.2 值繼承再修改

  值也可以被繼承再修改。

package cn.qlq.thread.ten;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 繼承再修改值
 * 
 * @author QiaoLiQiang
 * @time 2018年12月15日下午9:34:41
 * @param <T>
 */
public class Demo6<T> extends InheritableThreadLocal<String> {
    public static Demo6<String> t1 = new Demo6<String>();
    private static final Logger LOGGER = LoggerFactory.getLogger(Demo6.class);

    public static void main(String[] args) throws InterruptedException {
        // 主線程中存入值
        t1.set("存放的值" + Thread.currentThread().getName());
        LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get());

        // 創建子線程獲取值
        new Thread(new Runnable() {
            @Override
            public void run() {
                LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get());

                // 主線程中存入值
                t1.set("存放的值" + Thread.currentThread().getName());
                LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get());

                // 創建子子線程獲取值
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get());
                    }
                }, "thread2").start();
            }
        }, "thread1").start();

        Thread.sleep(2 * 1000);
        LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get());
    }
}

結果:(主線程中存入值,在子線程修改了值,main線程取到的值還是main中存入的值,子線程以及子子線程獲得的值是子線程修改的值。)

21:36:26 [cn.qlq.thread.ten.Demo6]-[INFO] threadName - >main,值->存放的值main
21:36:26 [cn.qlq.thread.ten.Demo6]-[INFO] threadName - >thread1,值->存放的值main
21:36:26 [cn.qlq.thread.ten.Demo6]-[INFO] threadName - >thread1,值->存放的值thread1
21:36:26 [cn.qlq.thread.ten.Demo6]-[INFO] threadName - >thread2,值->存放的值thread1
21:36:28 [cn.qlq.thread.ten.Demo6]-[INFO] threadName - >main,值->存放的值main

6.ThreadLocal 源碼解析

  ThreadLocal其實比較簡單,因為類裏就三個public方法:set(T value)、get()、remove()。

三個理論基礎:

1、每個線程都有一個自己的ThreadLocal.ThreadLocalMap對象 (ThreadLocalMap 是ThreadLocal的靜態內部類,一個類似於Map結構的普通類,沒有實現Map接口,也是內部維護一個靜態內部類Entry存放數據,而且其內部的Entry繼承 WeakReference弱引用(被若引用關聯的對象只能生存到下一次垃圾回收之前。其內部的key是Threadlocal,value就是存入的值)。)

Thread.class中的一個成員屬性:

    ThreadLocal.ThreadLocalMap threadLocals = null;

2、每一個ThreadLocal對象都有一個循環計數器
3、ThreadLocal.get()取值,就是根據當前的線程,獲取線程中自己的ThreadLocal.ThreadLocalMap,然後在這個Map中根據第二點中循環計數器取得一個特定value值

6.1 set(T value)源碼解讀

    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 ,如果 獲取到的ThreadLocalMap不為空的話就直接set值,否則走 createMap方法創建map。

(1)getmap(t)從線程中獲取 ThreadLocalMap (上面說過了每個Thread都有都有一個自己的ThreadLocal.ThreadLocalMap對象)

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

(2)map.set(this,value)設置值

        /**
         * 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);

            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();
        }
    /**
     * Returns the next hash code.
     */
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }

    private final int threadLocalHashCode = nextHashCode();

    /**
     * The next hash code to be given out. Updated atomically. Starts at
     * zero.
     */
    private static AtomicInteger nextHashCode =
        new AtomicInteger();

  這個Map存儲的方式不是鏈表法而是開地址法。看到設置table中的位置的時候,都把一個static的nextHashCode累加一下,這意味著,set的同一個value,可能在每個ThreadLocal.ThreadLocalMap中的table中的位置都不一樣。

  1. 先對ThreadLocal裏面的threadLocalHashCode取模獲取到一個table中的位置
  2. 這個位置上如果有數據,獲取這個位置上的ThreadLocal

  (1)判斷一下位置上的ThreadLocal和我本身這個ThreadLocal是不是一個ThreadLocal,是的話數據就覆蓋,返回

  (2)不是同一個ThreadLocal,再判斷一下位置上的ThreadLocal是是不是空的,這個解釋一下。Entry是ThreadLocalMap的一個靜態內部類,並且是弱引用,"static class Entry extends WeakReference<ThreadLocal>",有可能這個 Entry 被垃圾回收了,這時候把新設置的value替換到當前位置上,返回

  (3)上面都沒有返回,給模加1,看看模加1後的table位置上是不是空的,是空的再加1,判斷位置上是不是空的...一直到找到一個table上的位置不是空的為止,往這裏面塞一個value。換句話說,當table的位置上有數據的時候,ThreadLocal采取的是辦法是找最近的一個空的位置設置數據

3.這個位置上如果沒有數據,就創建一個Entry到這個位置。

(3) createMap(thread,value)方法查看:

    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
        ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

創建一個ThreadLocalMap並且將引用傳遞給線程對象的 threadLocals 。

ThreadLocalMap創建的時候做了一些初始化工作,並且將值設置進去。

6.2 get()源碼解讀

get()方法的源碼如下:

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }

獲取到當前線程-》獲取到當前線程裏面的ThreadLocalMap -》

                      如果ThreadLocalMap 為不為null,獲取其內部的Entry對象-》獲取entry的value(根據ThreadLocal 獲取一個下標,然後獲取對應下標的entry的信息)

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

                      如果ThreadLocalMap 為null,做默認設置並且返回默認值(這也是我們在上面的例子中繼承ThreadLocal重寫initialValue方法可以設置默認值的原因)

    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;
    }
    protected T initialValue() {
        return null;
    }

6.3 remove()源碼解讀

remove()源碼如下:

     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }
        private void remove(ThreadLocal key) {
            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)]) {
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

根據當前線程獲取到ThreadLocalMap-》如果獲取的ThreadLocalMap不為null,調用其remove(key)方法

remove(ThreadLocal)根據ThreadLocal 對象獲取一個下標i,如果tab[i]不為null,即存在對應的entry,調用entry的clear方法

補充:clear方法是Reference類型一個方法:--其作用就是將referent置為null,垃圾回收就可以回收此對象。

    public void clear() {
        this.referent = null;
    }

類ThreadLocal的使用與源碼分析