Java-ThreadLocal

定義

線程本地變量,也有些地方叫做線程本地存儲,其實意思差不多。ThreadLocal可以讓每個線程擁有一個屬于自己的變量的副本,不會和其他線程的變量副本沖突,實現(xiàn)了線程的數(shù)據(jù)隔離。

使用示例

public class ThreadLocalTest {

    private static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    static class MyThread extends Thread{
        @Override
        public void run() {
            super.run();
            threadLocal.set("son");
            System.out.println(threadLocal.get());  //son
        }
    }

    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
        threadLocal.set("main");
        System.out.println(threadLocal.get());  //main
    }
}

通過上面的代碼我們發(fā)現(xiàn),當(dāng)我我們使用同一個ThreadLocal對象在不同線程中set不同的值,會打印出不同的值,這是怎么做到的呢?上面定義中提到每個線程擁有一個屬于自己的變量的副本,這就能解通為什么我們可以獲取到不同的值。

既然每個線程有一個自己的變量副本,那我們自己是不是也可以實現(xiàn)呢?答案是肯定的,下面我們自己實現(xiàn)一個類似的功能。

//自定義實現(xiàn)的ThreadLocal
public class MyThreadLocal<T> {
    private Map<Thread, T> hashMap = new HashMap();
    
    //多線程下保證原子性
    public synchronized void set(T t) {
        synchronized (MyThreadLocal.this) {
            hashMap.put(Thread.currentThread(), t);
        }
    }

    public synchronized T get() {
        return hashMap.get(Thread.currentThread());
    }
}

public class ThreadLocalTest {
    //使用我們自己定義的ThreadLocal
    private static MyThreadLocal<String> threadLocal = new MyThreadLocal<>();

    static class MyThread extends Thread{
        @Override
        public void run() {
            super.run();
            threadLocal.set("son");
            System.out.println(threadLocal.get());  //son
        }
    }

    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
        threadLocal.set("main");
        System.out.println(threadLocal.get());  //main
    }
}

我們自己實現(xiàn)了一個MyThreadLocal對象,創(chuàng)建了一個Key是Thread的Map對象存儲對應(yīng)線程的數(shù)據(jù),也實現(xiàn)了ThreadLocal的功能,下面我們看看系統(tǒng)的ThreadLocal是怎么做的。

源碼分析

我們從set方法開始分析。

    public void set(T value) {
        // 獲取當(dāng)前線程
        Thread t = Thread.currentThread();
        //獲取ThreadLocalMap對象
        ThreadLocalMap map = getMap(t);
        if (map != null) //不為空直接調(diào)用ThreadLocalMap的set方法
            map.set(this, value);  
        else
            createMap(t, value);  //為空創(chuàng)建ThreadLocalMap 并將數(shù)據(jù)存儲起來
    }

然后跟進(jìn)getMap方法

ThreadLocalMap getMap(Thread t) {
        //這里調(diào)用到了Thread中,也就是說ThreadLocalMap對象是從Thread中獲取的
        return t.threadLocals;
    }

繼續(xù)看看Thread的threadLocals對象

    //Thread中定義的threadLocals屬性
    ThreadLocal.ThreadLocalMap threadLocals = null;

繼續(xù)看看ThreadLocalMap的set方法

 private void set(ThreadLocal<?> key, Object value) {
            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();
                //如果存在key則更新值
                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
            //沒有對應(yīng)的key則添加到數(shù)組中
            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

上面我們看到,存值的時候?qū)hreadLocal和Object(我們存儲的數(shù)據(jù))封裝到了一個Entry對象中,下面我們看下這個Entry

//ThreadLocalMap中的內(nèi)部類 存儲了ThreadLocal和我們保存的值
static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;
            //ThreadLocal作為key
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

這里我們可以總結(jié)下大致的流程
1、ThreadLocal調(diào)用set方法,然后獲取當(dāng)前調(diào)用的Thread
2、根據(jù)Thread獲取threadLocals屬性(ThreadLocalMap)
3、調(diào)用ThreadLocalMap的set方法
4、將ThreadLocal和我們保存的值封裝成Entry對象,添加到數(shù)組(table)中

我們知道了存儲的大致流程,獲取其實也是一樣的,都是對應(yīng)的對象調(diào)用流程,我們就不介紹了。

我們上面也實現(xiàn)了自己的MyThreadLocal對象,也完成了線程副本數(shù)據(jù)的存儲,我們思考下,這兩種方式有什么區(qū)別呢?系統(tǒng)為什么要做這么多操作步驟去實現(xiàn)線程副本數(shù)據(jù)呢?

我們自己的實現(xiàn)方式是創(chuàng)建了一個Map去存儲,當(dāng)多線程情況下,為了保證原子性,我們加了鎖。
如果當(dāng)前有很多線程要獲取數(shù)據(jù),那么這些線程都會去爭奪這個map,沒有拿到的就會阻塞,這樣性能上肯定是會有影響的。

系統(tǒng)的實現(xiàn)方式則是,沒個線程都有自己的map,不存在并發(fā)的問題,自己用自己的,效率無疑是要高的,所以系統(tǒng)的實現(xiàn)還是有一定道理的。

這就跟打籃球是一樣的,大家都搶一個球,和每個人都有一個球,效果肯定是不一樣的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容