Java ThreadLocal

最近改BUG的時候遇到一個與ThreadLocal相關的問題, 之前對這個ThreadLocal似乎有些沒怎麼弄明白, 特在網上找了資料理解了一番。下面的文章寫的通俗易懂,特此摘錄。
   
【本文轉載自:http://fuliang.iteye.com/blog/155148
    最早接觸ThreadLocal這個東東,還是在學Hibernate的時候,當時看ThreadLocal沒明白是幹什麼的,後來在網上查才明白ThreadLocal的用途,ThreadLocal其實蠻有用的,總結一下具體的原理及用法。
   雖然支持線程局部變量早就是許多線程工具,但 Java Threads API 的最初設計卻沒有這項有用的功能。而且,最初的實現也相當低效。ThreadLocal 極少受到關注,但對簡化線程安全併發程序的開發來說,它卻是很方便的。
  ThreadLocal要解決的是什麼問題呢?
一個本來應該線程安全的類,裏面有一個線程不安全的變量,這樣這個類也就線程不安全了,那應該怎麼辦呢?我們如果能夠把這個變量和每個線程綁定,也就是每一個線程擁有這個變量的副本,那麼整個對象就成爲線程安全的了。一個解決方案就是使用一個Map,key對應於當前的線程,value對應於那個變量,這樣我們就可以輕易的獲取到當前線程的那個變量的副本了,ThreadLocal就是這個東東。
我們不妨寫寫大致的代碼:
Java代碼  收藏代碼
  1. public class ThreadLocal{  
  2.  private Map values = Collections.synchronizedMap(new HashMap());  
  3.  public Object get(){  
  4.   Thread curThread = Thread.currentThread();   
  5.   Object o = values.get(curThread);   
  6.   if (o == null && !values.containsKey(curThread)){  
  7.    o = initialValue();  
  8.    values.put(curThread, o);   
  9.   }  
  10.   return o;   
  11.  }  
  12.   
  13.  public void set(Object newValue){  
  14.   values.put(Thread.currentThread(), newValue);  
  15.  }  
  16.   
  17.  public Object initialValue(){  
  18.   return null;   
  19.  }  
  20. }  

當然java的ThreadLocal實現的總體思路也大致如此。
我們看看jdk提供的api文檔:
T get()
          返回此線程局部變量的當前線程副本中的值,如果變量沒有用於當前線程的值,則先將其初始化爲調用 initialValue() 方法返回的值
protected  T initialValue()
          返回此線程局部變量的當前線程的“初始值”。
void remove()
          移除此線程局部變量當前線程的值。
void set(T value)
          將此線程局部變量的當前線程副本中的值設置爲指定值。
我們看jdk文檔提供了一個例子:
Java代碼  收藏代碼
  1. import java.util.concurrent.atomic.AtomicInteger;  
  2.   
  3.  public class UniqueThreadIdGenerator {  
  4.   
  5.      private static final AtomicInteger uniqueId = new AtomicInteger(0);  
  6.   
  7.      private static final ThreadLocal < Integer > uniqueNum =   
  8.          new ThreadLocal < Integer > () {  
  9.              @Override protected Integer initialValue() {  
  10.                  return uniqueId.getAndIncrement();  
  11.          }  
  12.      };  
  13.    
  14.      public static int getCurrentThreadId() {  
  15.          return uniqueId.get();//應該是return uniqueNum.get();  
  16.      }  
  17.  } // UniqueThreadIdGenerator  

這個例子我看的時候沒看懂,其實是有錯誤的 return uniqueId.get();,應該是return uniqueNum.get();我用的是中文翻譯過來的jdk api 1.6.0,不知道
大家的jdk幫助文檔有沒有這個問題.
我們看看Hibernate官方文檔提供的一個通過ThreadLocal維護Session的例子:
Java代碼  收藏代碼
  1.  public class HibernateUtil {  
  2.          private static final SessionFactory sessionFactory;  
  3.          static {  
  4.                    try {  
  5.                             sessionFactory = new Configuration().configure()  
  6.                                                .buildSessionFactory();  
  7.   
  8.                    } catch (Throwable ex) {  
  9.                             ex.printStackTrace();  
  10.                             throw new ExceptionInInitializerError(ex);  
  11.   
  12.                    }  
  13.   
  14.          }  
  15.          public static final ThreadLocal tLocalsess = new ThreadLocal();  
  16.          // 取得session   
  17.          public static Session currentSession() {  
  18.                    Session session = (Session) tLocalsess.get();  
  19.                    // 打開一個新的session,如果當前的不可用.  
  20.                    try {  
  21.                             if (session == null || !session.isOpen()) {  
  22.                                      session = openSession();  
  23.                                      tLocalsess.set(session);  
  24.                             }  
  25.   
  26.                    } catch (HibernateException e) {  
  27.                             // 拋出HibernateException異常  
  28.                             e.printStackTrace();  
  29.   
  30.                    }  
  31.                    return session;  
  32.          }  
  33.        public static void closeSession() {  
  34.                    Session session = (Session) tLocalsess.get();  
  35.                    tLocalsess.set(null);  
  36.                    try {  
  37.                             if (session != null && session.isOpen()) {  
  38.                                      session.close();  
  39.                             }  
  40.                    } catch (HibernateException e) {  
  41.                             //拋出 InfrastructureException異常  
  42.                    }  
  43.          }  
  44. //other code  
  45. }  
  46. 這段代碼藉助threadLocal,每一個線程保存一個session實例,從而避免線程內的頻繁創建和銷燬session.  

其它適合使用 ThreadLocal 但用池卻不能成爲很好的替代技術的應用程序包括存儲或累積每線程上下文信息以備稍後檢索之用這樣的應用程序。例如,假設您想創建一個用於管理多線程應用程序調試信息的工具。您可以用 DebugLogger 類作爲線程局部容器來累積調試信息。在一個工作單元的開頭,您清空容器,而當一個錯誤出現時,您查詢該容器以檢索這個工作單元迄今爲止生成的所有調試信息。
用 ThreadLocal 管理每線程調試日誌
Java代碼  收藏代碼
  1. public class DebugLogger {  
  2.   private static class ThreadLocalList extends ThreadLocal {  
  3.     public Object initialValue() {  
  4.       return new ArrayList();  
  5.     }  
  6.     public List getList() {   
  7.       return (List) super.get();   
  8.     }  
  9.   }  
  10.   private ThreadLocalList list = new ThreadLocalList();  
  11.   private static String[] stringArray = new String[0];  
  12.   public void clear() {  
  13.     list.getList().clear();  
  14.   }  
  15.   public void put(String text) {  
  16.     list.getList().add(text);  
  17.   }  
  18.   public String[] get() {  
  19.     return list.getList().toArray(stringArray);  
  20.   }  
  21. }  


   在您的代碼中,您可以調用 DebugLogger.put() 來保存您的程序正在做什麼的信息,而且,稍後如果有必要(例如發生了一個錯誤),您能夠容易地檢索與某個特定線程相關的調試信息。 與簡單地把所有信息轉儲到一個日誌文件,然後努力找出哪個日誌記錄來自哪個線程(還要擔心線程爭用日誌紀錄對象)相比,這種技術簡便得多,也有效得多。
ThreadLocal 在基於 servlet 的應用程序或工作單元是一個整體請求的任何多線程應用程序服務器中也是很有用的,因爲在處理請求的整個過程中將要用到單個線程。您可以通過每線程單子技術用 ThreadLocal 變量來存儲各種每請求(per-request)上下文信息。
    ThreadLocal 能帶來很多好處。它常常是把有狀態類描繪成線程安全的,或者封裝非線程安全類以使它們能夠在多線程環境中安全地使用的最容易的方式。使用 ThreadLocal 使我們可以繞過爲實現線程安全而對何時需要同步進行判斷的複雜過程,而且因爲它不需要任何同步,所以也改善了可伸縮性。除簡單之外,用 ThreadLocal 存儲每線程單子或每線程上下文信息在歸檔方面還有一個頗有價值好處:通過使用 ThreadLocal ,存儲在 ThreadLocal 中的對象都是 不被線程共享的是清晰的,從而簡化了判斷一個類是否線程安全的工作。
     當然ThreadLocal並不能替代同步機制,兩者面向的問題領域不同。同步機制是爲了同步多個線程對相同資源的併發訪問,是爲了多個線程之間進行通信的有效方式;而ThreadLocal是隔離多個線程的數據共享,從根本上就不在多個線程之間共享資源(變量),這樣當然不需要對多個線程進行同步了。所以,如果你需要進行多個線程之間進行通信,則使用同步機制;如果需要隔離多個線程之間的共享衝突,可以使用ThreadLocal,這將極大地簡化你的程序,使程序更加易讀、簡潔。
發佈了32 篇原創文章 · 獲贊 9 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章