天天看點

談談Java中的ThreadLocal

ThreadLocal介紹&跳出誤區

看看源碼

線程獨享變量?

  ThreadLocal一般稱為線程本地變量,它是一種特殊的線程綁定機制,将變量與線程綁定在一起,為每一個線程維護一個獨立的變量副本。通過ThreadLocal可以将對象的可見範圍限制在同一個線程内。

跳出誤區

  需要重點強調的的是,不要拿ThreadLocal和synchronized做類比,因為這種比較壓根就是無意義的!sysnchronized是一種互斥同步機制,是為了保證在多線程環境下對于共享資源的正确通路。而ThreadLocal從本質上講,無非是提供了一個“線程級”的變量作用域,它是一種線程封閉(每個線程獨享變量)技術,更直白點講,ThreadLocal可以了解為将對象的作用範圍限制在一個線程上下文中,使得變量的作用域為“線程級”。

  沒有ThreadLocal的時候,一個線程在其聲明周期内,可能穿過多個層級,多個方法,如果有個對象需要在此線程周期内多次調用,且是跨層級的(線程内共享),通常的做法是通過參數進行傳遞;而ThreadLocal将變量綁定線上程上,在一個線程周期内,無論“你身處何地”,隻需通過其提供的get方法就可輕松擷取到對象。極大地提高了對于“線程級變量”的通路便利性。

來看個簡單的例子

  假設我們要為每個線程關聯一個唯一的序号,在每個線程周期内,我們需要多次通路這個序号,這時我們就可以使用ThreadLocal了.(當然下面這個例子沒有完全展現出跨層級跨方法的調用,了解就可以了)

package concurrent;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * Created by chengxiao on 2016/12/12.
 */
public class ThreadLocalDemo {
    public static void main(String []args){
        for(int i=0;i<5;i++){
            final Thread t = new Thread(){
                @Override
                public void run(){
                    System.out.println("目前線程:"+Thread.currentThread().getName()+",已配置設定ID:"+ThreadId.get());
                }
            };
            t.start();
        }
    }
    static   class ThreadId{
        //一個遞增的序列,使用AtomicInger原子變量保證線程安全
        private static final AtomicInteger nextId = new AtomicInteger(0);
        //線程本地變量,為每個線程關聯一個唯一的序号
        private static final ThreadLocal<Integer> threadId =
                new ThreadLocal<Integer>() {
                    @Override
                    protected Integer initialValue() {
                        return nextId.getAndIncrement();//相當于nextId++,由于nextId++這種操作是個複合操作而非原子操作,會有線程安全問題(可能在初始化時就擷取到相同的ID,是以使用原子變量
                    }
                };

       //傳回目前線程的唯一的序列,如果第一次get,會先調用initialValue,後面看源碼就了解了
        public static int get() {
            return threadId.get();
        }
    }
}      

執行結果,可以看到每個線程都配置設定到了一個唯一的ID,同時在此線程範圍内的"任何地點",我們都可以通過ThreadId.get()這種方式直接擷取。

目前線程:Thread-4,已配置設定ID:1
目前線程:Thread-0,已配置設定ID:0
目前線程:Thread-2,已配置設定ID:3
目前線程:Thread-1,已配置設定ID:4
目前線程:Thread-3,已配置設定ID:2       

 set操作,為線程綁定變量

public void set(T value) {
    Thread t = Thread.currentThread();//1.首先擷取目前線程對象
        ThreadLocalMap map = getMap(t);//2.擷取該線程對象的ThreadLocalMap
        if (map != null)
            map.set(this, value);//如果map不為空,執行set操作,以目前threadLocal對象為key,實際存儲對象為value進行set操作
        else
            createMap(t, value);//如果map為空,則為該線程建立ThreadLocalMap
    }      

可以看到,ThreadLocal不過是個入口,真正的變量是綁定線上程上的。

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;//線程對象持有ThreadLocalMap的引用
}      

下面給是Thread類中的定義,每個線程對象都擁有一個ThreadLocalMap對象

ThreadLocal.ThreadLocalMap threadLocals = null;      

現在,我們能看出ThreadLocal的設計思想了:

1.ThreadLocal僅僅是個變量通路的入口;

2.每一個Thread對象都有一個ThreadLocalMap對象,這個ThreadLocalMap持有對象的引用;

3.ThreadLocalMap以目前的threadlocal對象為key,以真正的存儲對象為value。get時通過threadlocal執行個體就可以找到綁定在目前線程上的對象。

乍看上去,這種設計确實有些繞。我們完全可以在設計成Map<Thread,T>這種形式,一個線程對應一個存儲對象。ThreadLocal這樣設計的目的主要有兩個:

  一是可以保證目前線程結束時相關對象能盡快被回收;二是ThreadLocalMap中的元素會大大減少,我們都知道map過大更容易造成哈希沖突而導緻性能變差。

我們再來看看get方法

public T get() {
     Thread t = Thread.currentThread();//1.首先擷取目前線程
         ThreadLocalMap map = getMap(t);//2.擷取線程的map對象
         if (map != null) {//3.如果map不為空,以threadlocal執行個體為key擷取到對應Entry,然後從Entry中取出對象即可。
             ThreadLocalMap.Entry e = map.getEntry(this);
             if (e != null)
                 return (T)e.value;
         }
         return setInitialValue();//如果map為空,也就是第一次沒有調用set直接get(或者調用過set,又調用了remove)時,為其設定初始值
     }      
setInitialValue      
1  private T setInitialValue() {
 2         T value = initialValue();//擷取初始值
 3         Thread t = Thread.currentThread();
 4         ThreadLocalMap map = getMap(t);
 5         if (map != null)
 6             map.set(this, value);
 7         else
 8             createMap(t, value);
 9         return value;
10     }      

initialValue方法,預設是null,通路權限是protected,即允許重寫。

1 protected T initialValue() {
2         return null;
3     }      

 談到這兒,我們應該已經對ThreadLocal的設計目的及設計思想有一定的了解了。

  還有一個會引起疑惑的問題,我們說ThreadLocal為每一個線程維護一個獨立的變量副本,那麼是不是說各個線程之間真正的做到對于對象的“完全自治”而不對其他線程的對象産生影響呢?其實這已經不屬于對于ThreadLocal的讨論,而是你出于何種目的去使用ThreadLocal。如果我們為一個線程關聯的對象是“完全獨享”的,也就是每個線程擁有一整套的新的 棧中的對象引用+堆中的對象,那麼這種情況下是真正的徹底的“線程獨享變量”,相當于一種深度拷貝,每個線程自己玩自己的,對該對象做任何的操作也不會對别的線程有任何影響。

  另一種更普遍的情況,所謂的獨享變量副本,其實也就是每個線程都擁有一個獨立的對象引用,而堆中的對象還是線程間共享的,這種情況下,自然還是會涉及到對共享資源的通路操作,依然會有線程不安全的風險。是以說,ThreadLocal無法解決線程安全問題。

  是以,需不需要完全獨享變量,進行完全隔離,就取決于你的應用場景了。可以想象,對象過大的時候,如果每個線程都有這麼一份“深拷貝”,并發又比較大,對于伺服器的壓力自然是很大的。像web開發中的servlet,servlet是線程不安全的,一請求一線程,多個線程共享一個servlet對象;而早期的CGI設計中,N個請求就對應N個對象,并發量大了之後性能自然就很差。

談談Java中的ThreadLocal
  ThreadLocal在spring的事務管理,包括Hibernate的session管理等都有出現,在web開發中,有時會用來管理使用者會話 HttpSession,web互動中這種典型的一請求一線程的場景似乎比較适合使用ThreadLocal,但是需要特别注意的是,由于此時session與線程關聯,而tomcat這些web伺服器多會采用線程池機制,也就是說線程是可複用的,是以在每一次進入的時候都需要重新進行set,或者在結束時及時remove。      

作者:

dreamcatcher-cx

出處:

<http://www.cnblogs.com/chengxiao/>

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在頁面明顯位置給出原文連結。