swzhebei

          導航

          <2012年5月>
          293012345
          6789101112
          13141516171819
          20212223242526
          272829303112
          3456789

          留言簿(5)

          隨筆檔案

          閱讀排行榜

          評論排行榜

          常用鏈接

          統計

          最新評論

          • 1.?re: 調用百度地圖小實例
          • 如果我有100個經緯度 請問,您是不是再代碼里寫100個?你這樣沒有價值,如何獲取動態的請說明!
          • --toly
          • 2.?re: 調用百度地圖小實例
          • 更改經緯度就不行了!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
          • --你姥姥

          ThreadLocal類(1)

          1.目的

          ThreadLocal目的是保存一些線程級別的全局變量,比如connection,或者事務上下文,避免這些值需要一直通過函數參數的方式一路傳遞。

          2. 常見用法
          舉例其中一種常見用法:

          public class Test2 {
          	public static void main(String[] args) throws InterruptedException {
          		testThreadLocal();
          	}
           
          	private static void testThreadLocal() {
          		Util.setGlobalName("zili.dengzl");
          		new Foo().printName();
          	}
          }
           
          class Foo{
          	public void printName(){
          		System.out.println("globalName="+Util.getGlobalName());
          	}
          }
           
          class Util {
          	private static final ThreadLocal<String> globalName = new ThreadLocal<String>();
           
          	public static String getGlobalName() {
          		return globalName.get();
          	}
           
          	public static void setGlobalName(String name) {
          		globalName.set(name);
          	}
          }

          3.實現分析

          要實現上面這樣的功能,最簡單的想法是用一個Map<Thread,T>,如下:

          class MockThreadLocal<T> {
          	private Map<Thread, T> map = new HashMap<Thread, T>();
           
          	public T get() {
          		return (T) map.get(Thread.currentThread());
          	}
           
          	public void set(T value) {
          		map.put(Thread.currentThread(), value);
          	}
          }

          這樣也能實現ThreadLocal的效果,但是有一個問題,當對應的線程消失后,map中對應的線程值并不會被回收,從而造成內存泄露。

          事實上ThreadLocal是這樣做的:

          每個Thread都有一個threadLocalMap,key是threadLocal對象,value是具體使用的值。ThreadLocal對象的get就是先取得當前的Thread,然后從這個Thread的threadLcoalMap中取出值。set類似。

          下面看下具體代碼:

              /**
               * Returns the value in the current thread's copy of this
               * thread-local variable.  If the variable has no value for the
               * current thread, it is first initialized to the value returned
               * by an invocation of the {@link #initialValue} method.
               *
               * @return the current thread's value of this thread-local
               */
              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();
              }

          注意這里如果取到沒有該線程對應的值,會調用setInitialValue();,最終調用initialValue()生成一個值,這也是我們很多場景下要override這個方法的原因;

           

          下面看一下getMap(Thread t)方法:

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

          在Thread類中:

              /* ThreadLocal values pertaining to this thread. This map is maintained
               * by the ThreadLocal class. */
              ThreadLocal.ThreadLocalMap threadLocals = null;

          由此可見,所有的ThreadLocal的信息,最終是關聯到Thread上的,線程消失后,對應的Thread對象也被回收,這時對應的ThreadLocal對象(該線程部分)也會被回收。

          這里為什么是一個ThreadLocalMap呢,因為一個線程可以有多個ThreadLocal變量,通過map.getEntry(this)取得對應的某個具體的變量。

                  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的Entry是一個weakReference:

                 /**
                   * 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的key是ThreadLocal對象,如果某個ThreadLocal對象所有的強引用沒有了,會利用weakref的功能把他回收掉,然后復用這個entry。

          考慮一下如果不用weakReference會出現什么情況:假設某個對象是這樣引用的

          private final ThreadLocal<String> globalName = new ThreadLocal<String>();

          注意沒有static,然后這個對象被不斷的new出來,然后死掉,每次ThreadLocalmap中都會多出一個entry,然后這個entry強引用一個ThreadLocal對象,ThreadLocalMap本身就沒有辦法確定哪個entry是不用了的,如果恰好這個線程是線程池中的,會存活很久,那就杯具了。

          ThreadLocalMap用了weakReference,失去強引用的ThreadLocal對象會在下次gc時被回收,然后ThreadLocalMap本身在get和set的時候會考察key為空的Entry,并復用它或者清除,從而避免內存泄露。

          這樣看來,HashMap也有一樣的問題,但為什么hashMap不這樣呢,因為hashMap的put是業務代碼操作的,因此如果有長期存活的HashMap,(比如static的)業務代碼put進去就有義務去remove,但ThreadLocal的put操作時ThreadLocal類干的,業務代碼不知道,因此也不會去做remove,而ThreadLocalMap本身不知道引用他的某個entry的key的對象什么時候死掉了,那么如果不用弱引用,就不知道這個ThreadLocal對象什么時候需要回收了。

          附:

          這里補充一下weakReference的用法供參考(當強引用不存在時,下次垃圾回收會回收弱引用所引用的對象):

          		Object o = new Object();
          		WeakReference<Object> ref = new WeakReference<Object>(o);
          		System.out.println(ref.get());
          		o=null;
          		System.gc();
          		System.out.println(ref.get());

          結果輸出:

           java.lang.Object@de6ced
           null

          4. FAQ

          4.1 為什么一般的ThreadLocal用法都要加static,如下:

          class Test {
              private static final ThreadLocal<String> globalName = new ThreadLocal<String>();
          }
          answer:事實上,不一定是要static,但使用它的對象在業務需要范圍類一定要是單例。因為根據前面的分析,ThreadLocalMap是以ThreadLocal對象為key的,如果Test類不是static,也不是單例的,那么兩個Test對象就有兩個key,取出來的數據肯定不同

           

          class TestThreadLocal{    
              public static void main(String[] args) {
          		Test t1 = new Test();
          		Test t2 = new Test();
           
          		t1.pool.set("a");
          		System.out.println(t1.pool.get());
          		System.out.println(t2.pool.get());
          	}
          }
          class Test{
          	public ThreadLocal pool = new ThreadLocal();
          }

          輸出將會是:a,null

          原因就無需多解釋了。唯一需要啰嗦的一點是,就算一般情況都是單例,上面那個weakreference還是必要的,因為作為框架代碼,不能保證正常使用的情況下一個線程有很多ThreadLocal,如果不用weakreference,就會有內存泄漏的風險,特別是針對線程池中的線程。

          posted on 2012-05-31 13:15 透明的魚 閱讀(593) 評論(0)  編輯  收藏


          只有注冊用戶登錄后才能發表評論。


          網站導航:
           
          主站蜘蛛池模板: 永修县| 乌兰县| 克拉玛依市| 托克逊县| 定兴县| 青田县| 简阳市| 冕宁县| 宁陕县| 思南县| 津南区| 蛟河市| 社旗县| 云阳县| 西城区| 清水河县| 稷山县| 耿马| 综艺| 洛隆县| 年辖:市辖区| 灌云县| 衡水市| 富川| 四川省| 昌黎县| 威远县| 夏津县| 丰镇市| 蒙山县| 寿宁县| 玛曲县| 兴国县| 福贡县| 连云港市| 都江堰市| 桓仁| 常山县| 安仁县| 卓尼县| 鄂尔多斯市|