Java源碼分析 - ThreadLocal__Java

來源:互聯網
上載者:User
為什麼要寫這篇文章

網上很多文章都在講ThreadLocal的意義所在,然後大部分都在說ThreadLocal是為瞭解決安全執行緒而生的,旨在解決並發安全問題,這種說法是片面的,導致很多人理解不到ThreadLocal真正用途 ThreadLocal是什麼

ThreadLocal翻譯過來是線程局部變數,而不是本地線程。

ThreadLocal是為瞭解決在一個線程中,某個或者某些資源在不同層次的代碼中通過參數來回傳遞的問題,但是要在沒有增加效能損耗的情況下,保證安全執行緒。 為什麼要用ThreadLocal 單資源單安全執行緒

比如我們有兩個模組,一個把資源(靜態變數)增加100,一個把資源減少100,但是我們不想把資源通過參數傳入,只能把這個資源設定為靜態變數。
單線程執行個體代碼:

import java.util.concurrent.atomic.AtomicInteger;public class IncThread implements Runnable{    private static  int VALUE = 0;    @Override    public void run() {        iscr100();//模組1, 增加100        descr100();//模組2, 減少100    }    public void descr100(){        for(int i = 0; i< 1000; i++){            VALUE--;            try {            //類比IO,讓CPC進行切換            Thread.sleep(1);            } catch (InterruptedException e) {            }        }    }    public void iscr100(){        for(int i = 0; i< 1000; i++){            VALUE++;            try {            //類比IO,讓CPC進行切換            Thread.sleep(1);            } catch (InterruptedException e) {            }        }    }    public static void main(String[] args) throws InterruptedException {        IncThread myInc = new IncThread();        Thread thread = new Thread(myInc);        thread.start();        //保證主線程退出時,其他線程也執行完了        Thread.sleep(10000);        //列印最終結果        System.out.println(VALUE);    }}

單線程下沒有問題,上面的樣本VALUE的最終結果肯定是0。 單資源多線程不安全

但是如果是多線程下會正確麼。 我們修改main方法,改用兩個線程執行:

    public static void main(String[] args) throws InterruptedException {        IncThread myInc1 = new IncThread();        Thread thread1 = new Thread(myInc1);        Thread thread2 = new Thread(myInc1);        thread1.start();        thread2.start();        //保證主線程退出時,其他線程也執行完了        Thread.sleep(10000);        //列印最終結果        System.out.println(VALUE);    }

執行的結果肯定不是0,因為int的++和–操作不是安全執行緒的。
當然為瞭解決這個問題,我們可以把VALUE設定成安全執行緒的類,比如AtomicInteger,這針對於資源是唯一的情況下可以這樣做,雖然這麼做有一定的效能損耗,這個是沒有辦法的,但是如果是多個資源,每個線程都可以分配到一個資源的情況下,使用安全執行緒類會降低效能,再說,萬一我們的資源不是安全執行緒的呢,比如資料庫連接。 多資源多線程怎麼安全

這個時候ThreadLocal就登上曆史舞台了。

import java.util.concurrent.atomic.AtomicInteger;public class IncThread implements Runnable{    private static  ThreadLocal<Integer> VALUE = new ThreadLocal<>();    @Override    public void run() {        VALUE.set(0);//初始化資源,比如擷取資料庫連接池        iscr100();//模組1, 增加100        descr100();//模組2, 減少100        System.out.println(VALUE.get());    }    public void descr100(){        for(int i = 0; i< 1000; i++){            try {                Integer integer = VALUE.get();                integer++;                //類比IO,讓CPC進行切換            Thread.sleep(1);            } catch (InterruptedException e) {            }        }    }    public void iscr100(){        for(int i = 0; i< 1000; i++){            Integer integer = VALUE.get();            integer--;            try {            //類比IO,讓CPC進行切換            Thread.sleep(1);            } catch (InterruptedException e) {            }        }    }    public static void main(String[] args) throws InterruptedException {        IncThread myInc1 = new IncThread();        Thread thread1 = new Thread(myInc1);        Thread thread2 = new Thread(myInc1);        thread1.start();        thread2.start();        //保證主線程退出時,其他線程也執行完了        Thread.sleep(10000);    }}

到此為止,我們講解了ThreadLocal的意義所在,不過有人會說了,像這種多個資源的同步的情況下,我們可以自己實現呀,比如使用Map,以線程名稱作為KEY,以資源作為VALUE,提出這個方案的人員是很聰明的,ThreadLocal也確實是使用Map作為儲存的,但是這個方案有兩個壞處。
1. 通用性差: 每個架構的線程名稱是不定的,比如Spring架構、Servlet等等,很難,也是沒辦法有一個通用的Map能實現這個功能。
2. 易用性低:我們自己實現一套機制,很難使用到別人已有的架構中,只能在我們自己的架構裡使用。
那麼,我們就不得不選擇ThreadLocal了。 ThreadLocal源碼分析

既然ThreadLocal是為瞭解決多資源多線程下資源到處傳遞的問題,那麼ThreadLocal至少要有set, get和delete這個三個方法,實際上ThreadLocal有5個方法:

描述符和傳回值 方法名 描述
T get() Returns the value in the current thread’s copy of this thread-local variable.
protected T initialValue() Returns the current thread’s “initial value” for this thread-local variable.
void remove() Removes the current thread’s value for this thread-local variable.
void set(T value) Sets the current thread’s copy of this thread-local variable to the specified value.
static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) Creates a thread local variable.
Set方法
    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);    }

可以看出,ThreadLocal能實現跨代碼層次擷取線程資源的根本原因是有了Thread.currentThread() 這個靜態方法,這樣ThreadLocal可以隨時隨地做任何操作,而不需要傳入線程名稱或者ID。

我們看下getMap(t)做了什麼事情。

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

而Thread類有一個成員變數:
ThreadLocal.ThreadLocalMap threadLocals = null;
那麼我們就能理解到,ThreadLocal儲存的資源實際上儲存到了Thread上,ThreadLocal只是作為一個能找到這個資源的索引。 get()方法

我們知道了ThreadLocal執行個體是作為當前資源的索引,那麼get()方法的源碼就應運而生了。

    public T get() {        Thread t = Thread.currentThread();        //擷取到當前線程的ThreadLocalMap        ThreadLocalMap map = getMap(t);        //map存在時        if (map != null) {            //擷取到資源            ThreadLocalMap.Entry e = map.getEntry(this);            if (e != null) {                @SuppressWarnings("unchecked")                T result = (T)e.value;                return result;            }        }        //map不存在或者沒有set資源時,返回初始化的值        return setInitialValue();    }

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

如果get資源時不存在,則最終會調用initialValue()方法,初始化資源:

    protected T initialValue() {        return null;    }

但是這個方法會最終返回null,所以如果我們set資源時,如果調用get會直接返回null。 withInitial() 靜態方法

為了不get到null,避免我們還要判斷是否為null時再初始化ThreadLocal,ThreadLocal提供給我們一個靜態方法,可以傳入一個function來初始化這個ThreadLocal。

    public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {        return new SuppliedThreadLocal<>(supplier);    }
remove()方法

remove()就比較簡單了,這裡就不介紹了。

     public void remove() {         ThreadLocalMap m = getMap(Thread.currentThread());         if (m != null)             // 當前線程的ThreadLocalMap存在時刪除此資源。             m.remove(this);     }
最後

最後我們通過官方的解釋來總結一下ThreadLocal:
ThreadLocal提供了線程局部變數,這些變數和普通的變數是不一樣的,因為每一個線程的ThreadLocal都有自己的副本,並且是獨立初始化的副本,其他線程是無法訪問到的。使用ThreadLocal通常是與線程關聯類別中的私人靜態欄位,比如使用者Id和事務Id等。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.