瘋狂Java學習筆記(73)-----------ThreadLocal

      網(wǎng)友投稿 568 2025-04-02

      瘋狂Java學習筆記(73)-----------ThreadLocal


      Java并發(fā)編程:深入剖析ThreadLocal

      想必很多朋友對ThreadLocal并不陌生,今天我們就來一起探討下ThreadLocal的使用方法和實現(xiàn)原理。首先,本文先談一下對ThreadLocal的理解,然后根據(jù)ThreadLocal類的源碼分析了其實現(xiàn)原理和使用需要注意的地方,最后給出了兩個應用場景。

      以下是本文目錄大綱:

      一.對ThreadLocal的理解

      二.深入解析ThreadLocal類

      三.ThreadLocal的應用場景

      一.對ThreadLocal的理解

      ThreadLocal,很多地方叫做線程本地變量,也有些地方叫做線程本地存儲,其實意思差不多。可能很多朋友都知道ThreadLocal為變量在每個線程中都創(chuàng)建了一個副本,那么每個線程可以訪問自己內(nèi)部的副本變量。

      這句話從字面上看起來很容易理解,但是真正理解并不是那么容易。

      我們還是先來看一個例子:

      class ConnectionManager {

      private static Connection connect = null;

      public static Connection openConnection() {

      if(connect == null){

      connect = DriverManager.getConnection();

      }

      return connect;

      }

      public static void closeConnection() {

      if(connect!=null)

      connect.close();

      }

      }

      假設有這樣一個數(shù)據(jù)庫鏈接管理類,這段代碼在單線程中使用是沒有任何問題的,但是如果在多線程中使用呢?

      很顯然,在多線程中使用會存在線程安全問題:

      第一,這里面的2個方法都沒有進行同步,很可能在openConnection方法中會多次創(chuàng)建connect;

      第二,由于connect是共享變量,那么必然在調(diào)用connect的地方需要使用到同步來保障線程安全,因為很可能一個線程在使用connect進行數(shù)據(jù)庫操作,而另外一個線程調(diào)用closeConnection關閉鏈接。

      所以出于線程安全的考慮,必須將這段代碼的兩個方法進行同步處理,并且在調(diào)用connect的地方需要進行同步處理。

      這樣將會大大影響程序執(zhí)行效率,因為一個線程在使用connect進行數(shù)據(jù)庫操作的時候,其他線程只有等待。

      那么大家來仔細分析一下這個問題,這地方到底需不需要將connect變量進行共享?事實上,是不需要的。假如每個線程中都有一個connect變量,各個線程之間對connect變量的訪問實際上是沒有依賴關系的,即一個線程不需要關心其他線程是否對這個connect進行了修改的。

      到這里,可能會有朋友想到,既然不需要在線程之間共享這個變量,可以直接這樣處理,在每個需要使用數(shù)據(jù)庫連接的方法中具體使用時才創(chuàng)建數(shù)據(jù)庫鏈接,然后在方法調(diào)用完畢再釋放這個連接。比如下面這樣:

      class ConnectionManager {

      private Connection connect = null;

      public Connection openConnection() {

      if(connect == null){

      connect = DriverManager.getConnection();

      }

      return connect;

      }

      public void closeConnection() {

      if(connect!=null)

      connect.close();

      }

      }

      class Dao{

      public void insert() {

      ConnectionManager connectionManager = new ConnectionManager();

      Connection connection = connectionManager.openConnection();

      //使用connection進行操作

      connectionManager.closeConnection();

      }

      }

      這樣處理確實也沒有任何問題,由于每次都是在方法內(nèi)部創(chuàng)建的連接,那么線程之間自然不存在線程安全問題。但是這樣會有一個致命的影響:導致服務器壓力非常大,并且嚴重影響程序執(zhí)行性能。由于在方法中需要頻繁地開啟和關閉數(shù)據(jù)庫連接,這樣不盡嚴重影響程序執(zhí)行效率,還可能導致服務器壓力巨大。

      那么這種情況下使用ThreadLocal是再適合不過的了,因為ThreadLocal在每個線程中對該變量會創(chuàng)建一個副本,即每個線程內(nèi)部都會有一個該變量,且在線程內(nèi)部任何地方都可以使用,線程之間互不影響,這樣一來就不存在線程安全問題,也不會嚴重影響程序執(zhí)行性能。

      但是要注意,雖然ThreadLocal能夠解決上面說的問題,但是由于在每個線程中都創(chuàng)建了副本,所以要考慮它對資源的消耗,比如內(nèi)存的占用會比不使用ThreadLocal要大。

      二.深入解析ThreadLocal類

      在上面談到了對ThreadLocal的一些理解,那我們下面來看一下具體ThreadLocal是如何實現(xiàn)的。

      先了解一下ThreadLocal類提供的幾個方法:

      public T get() { }

      public void set(T value) { }

      public void remove() { }

      protected T initialValue() { }

      get()方法是用來獲取ThreadLocal在當前線程中保存的變量副本,set()用來設置當前線程中變量的副本,remove()用來移除當前線程中變量的副本,initialValue()是一個protected方法,一般是用來在使用時進行重寫的,它是一個延遲加載方法,下面會詳細說明。

      首先我們來看一下ThreadLocal類是如何為每個線程創(chuàng)建一個變量的副本的。

      先看下get方法的實現(xiàn):

      第一句是取得當前線程,然后通過getMap(t)方法獲取到一個map,map的類型為ThreadLocalMap。然后接著下面獲取到鍵值對,注意這里獲取鍵值對傳進去的是? this,而不是當前線程t。

      如果獲取成功,則返回value值。

      如果map為空,則調(diào)用setInitialValue方法返回value。

      我們上面的每一句來仔細分析:

      首先看一下getMap方法中做了什么:

      可能大家沒有想到的是,在getMap中,是調(diào)用當期線程t,返回當前線程t中的一個成員變量threadLocals。

      那么我們繼續(xù)取Thread類中取看一下成員變量threadLocals是什么:

      實際上就是一個ThreadLocalMap,這個類型是ThreadLocal類的一個內(nèi)部類,我們繼續(xù)取看ThreadLocalMap的實現(xiàn):

      可以看到ThreadLocalMap的Entry繼承了WeakReference,并且使用ThreadLocal作為鍵值。

      然后再繼續(xù)看setInitialValue方法的具體實現(xiàn):

      很容易了解,就是如果map不為空,就設置鍵值對,為空,再創(chuàng)建Map,看一下createMap的實現(xiàn):

      至此,可能大部分朋友已經(jīng)明白了ThreadLocal是如何為每個線程創(chuàng)建變量的副本的:

      首先,在每個線程Thread內(nèi)部有一個ThreadLocal.ThreadLocalMap類型的成員變量threadLocals,這個threadLocals就是用來存儲實際的變量副本的,鍵值為當前ThreadLocal變量,value為變量副本(即T類型的變量)。

      初始時,在Thread里面,threadLocals為空,當通過ThreadLocal變量調(diào)用get()方法或者set()方法,就會對Thread類中的threadLocals進行初始化,并且以當前ThreadLocal變量為鍵值,以ThreadLocal要保存的副本變量為value,存到threadLocals。

      然后在當前線程里面,如果要使用副本變量,就可以通過get方法在threadLocals里面查找。

      下面通過一個例子來證明通過ThreadLocal能達到在每個線程中創(chuàng)建變量副本的效果:

      public class Test {

      ThreadLocal longLocal = new ThreadLocal();

      ThreadLocal stringLocal = new ThreadLocal();

      public void set() {

      longLocal.set(Thread.currentThread().getId());

      stringLocal.set(Thread.currentThread().getName());

      }

      public long getLong() {

      return longLocal.get();

      }

      public String getString() {

      return stringLocal.get();

      }

      public static void main(String[] args) throws InterruptedException {

      final Test test = new Test();

      test.set();

      System.out.println(test.getLong());

      System.out.println(test.getString());

      Thread thread1 = new Thread(){

      public void run() {

      test.set();

      System.out.println(test.getLong());

      System.out.println(test.getString());

      };

      };

      thread1.start();

      thread1.join();

      System.out.println(test.getLong());

      System.out.println(test.getString());

      }

      }

      這段代碼的輸出結(jié)果為:

      從這段代碼的輸出結(jié)果可以看出,在main線程中和thread1線程中,longLocal保存的副本值和stringLocal保存的副本值都不一樣。最后一次在main線程再次打印副本值是為了證明在main線程中和thread1線程中的副本值確實是不同的。

      總結(jié)一下:

      1)實際的通過ThreadLocal創(chuàng)建的副本是存儲在每個線程自己的threadLocals中的;

      2)為何threadLocals的類型ThreadLocalMap的鍵值為ThreadLocal對象,因為每個線程中可有多個threadLocal變量,就像上面代碼中的longLocal和stringLocal;

      3)在進行get之前,必須先set,否則會報空指針異常;

      如果想在get之前不需要調(diào)用set就能正常訪問的話,必須重寫initialValue()方法。

      因為在上面的代碼分析過程中,我們發(fā)現(xiàn)如果沒有先set的話,即在map中查找不到對應的存儲,則會通過調(diào)用setInitialValue方法返回i,而在setInitialValue方法中,有一個語句是T value = initialValue(), 而默認情況下,initialValue方法返回的是null。

      看下面這個例子:

      public class Test {

      ThreadLocal longLocal = new ThreadLocal();

      ThreadLocal stringLocal = new ThreadLocal();

      public void set() {

      longLocal.set(Thread.currentThread().getId());

      stringLocal.set(Thread.currentThread().getName());

      }

      public long getLong() {

      return longLocal.get();

      }

      public String getString() {

      return stringLocal.get();

      }

      public static void main(String[] args) throws InterruptedException {

      final Test test = new Test();

      System.out.println(test.getLong());

      System.out.println(test.getString());

      Thread thread1 = new Thread(){

      public void run() {

      test.set();

      System.out.println(test.getLong());

      System.out.println(test.getString());

      };

      };

      thread1.start();

      thread1.join();

      System.out.println(test.getLong());

      System.out.println(test.getString());

      }

      }

      在main線程中,沒有先set,直接get的話,運行時會報空指針異常。

      但是如果改成下面這段代碼,即重寫了initialValue方法:

      public class Test {

      ThreadLocal longLocal = new ThreadLocal(){

      protected Long initialValue() {

      return Thread.currentThread().getId();

      };

      };

      ThreadLocal stringLocal = new ThreadLocal(){;

      protected String initialValue() {

      return Thread.currentThread().getName();

      };

      };

      public void set() {

      longLocal.set(Thread.currentThread().getId());

      stringLocal.set(Thread.currentThread().getName());

      }

      public long getLong() {

      return longLocal.get();

      }

      public String getString() {

      return stringLocal.get();

      }

      public static void main(String[] args) throws InterruptedException {

      final Test test = new Test();

      test.set();

      System.out.println(test.getLong());

      System.out.println(test.getString());

      Thread thread1 = new Thread(){

      public void run() {

      test.set();

      System.out.println(test.getLong());

      System.out.println(test.getString());

      };

      };

      thread1.start();

      thread1.join();

      System.out.println(test.getLong());

      System.out.println(test.getString());

      }

      }

      就可以直接不用先set而直接調(diào)用get了。

      三.ThreadLocal的應用場景

      最常見的ThreadLocal使用場景為 用來解決 數(shù)據(jù)庫連接、Session管理等。

      如:

      private static ThreadLocal connectionHolder

      = new ThreadLocal() {

      public Connection initialValue() {

      return DriverManager.getConnection(DB_URL);

      }

      };

      public static Connection getConnection() {

      return connectionHolder.get();

      瘋狂Java學習筆記(73)-----------ThreadLocal

      }

      下面這段代碼摘自:

      http://www.iteye.com/topic/103804

      private static final ThreadLocal threadSession = new ThreadLocal();

      public static Session getSession() throws InfrastructureException {

      Session s = (Session) threadSession.get();

      try {

      if (s == null) {

      s = getSessionFactory().openSession();

      threadSession.set(s);

      }

      } catch (HibernateException ex) {

      throw new InfrastructureException(ex);

      }

      return s;

      }

      參考資料:

      《深入理解Java虛擬機》

      《Java編程思想》

      http://ifeve.com/thread-management-10/

      http://www.ibm.com/developerworks/cn/java/j-threads/index3.html

      http://www.iteye.com/topic/103804

      http://www.iteye.com/topic/777716

      http://www.iteye.com/topic/757478

      http://blog.csdn.net/ghsau/article/details/15732053

      http://ispring.iteye.com/blog/162982

      http://blog.csdn.net/imzoer/article/details/8262101

      http://www.blogjava.net/wumi9527/archive/2010/09/10/331654.html

      http://bbs.csdn.net/topics/380049261

      http://www.cnblogs.com/dolphin0520/p/3920407.html

      Java 任務調(diào)度

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔相應法律責任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔相應法律責任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。

      上一篇:技術分享 | 被測系統(tǒng)架構(gòu)與數(shù)據(jù)流分析
      下一篇:AGGREGATE函數(shù)怎么忽略錯誤值計算
      相關文章
      久久精品国产亚洲夜色AV网站| 亚洲邪恶天堂影院在线观看| 亚洲色欲久久久综合网东京热| 亚洲hairy多毛pics大全| 亚洲一区二区三区无码国产| 亚洲av永久无码精品古装片 | 亚洲国产中文v高清在线观看| 亚洲a级成人片在线观看| 亚洲国产成人精品激情| 亚洲人成激情在线播放| 亚洲国产综合专区在线电影| 亚洲成a人片77777老司机| 亚洲人成网亚洲欧洲无码久久| 国产亚洲真人做受在线观看| 九月婷婷亚洲综合在线| 国产精品亚洲精品久久精品| 亚洲日韩在线中文字幕综合| 国产成人久久精品亚洲小说| 亚洲无码一区二区三区 | 亚洲人成影院午夜网站| 亚洲国产激情在线一区| 亚洲男人的天堂网站| 亚洲an日韩专区在线| 亚洲精品国产suv一区88| 另类图片亚洲校园小说区| 九月丁香婷婷亚洲综合色| 2022中文字字幕久亚洲| 亚洲精品国产精品乱码不卡√| 亚洲成a人片在线观看中文动漫| 亚洲欧洲日韩国产综合在线二区| 亚洲欧洲精品在线| 亚洲影院天堂中文av色| 日本中文一区二区三区亚洲| 国产亚洲AV夜间福利香蕉149| 亚洲国产精品无码久久一线| 亚洲精品无码久久毛片波多野吉衣| 亚洲乱码卡一卡二卡三| 亚洲av成人一区二区三区观看在线 | 亚洲综合色在线观看亚洲| 国产亚洲一区二区三区在线观看| 亚洲综合精品一二三区在线|