亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 學院 > 開發設計 > 正文

理解ThreadLocal

2019-11-09 16:06:29
字體:
來源:轉載
供稿:網友

ThreadLocal是什么

  早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal為解決多線程程序的并發問題提供了一種新的思路。使用這個工具類可以很簡潔地編寫出優美的多線程程序。

  當使用ThreadLocal維護變量時,ThreadLocal為每個使用該變量的線程提供獨立的變量副本,所以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應的副本。

  從線程的角度看,目標變量就象是線程的本地變量,這也是類名中“Local”所要表達的意思。

  所以,在Java中編寫線程局部變量的代碼相對來說要笨拙一些,因此造成線程局部變量沒有在Java開發者中得到很好的普及。

ThreadLocal的接口方法

ThreadLocal類接口很簡單,只有4個方法,我們先來了解一下:

void set(Object value)設置當前線程的線程局部變量的值。public Object get()該方法返回當前線程所對應的線程局部變量。public void remove()將當前線程局部變量的值刪除,目的是為了減少內存的占用,該方法是JDK 5.0新增的方法。需要指出的是,當線程結束后,對應該線程的局部變量將自動被垃圾回收,所以顯式調用該方法清除線程的局部變量并不是必須的操作,但它可以加快內存回收的速度。PRotected Object initialValue()返回該線程局部變量的初始值,該方法是一個protected的方法,顯然是為了讓子類覆蓋而設計的。這個方法是一個延遲調用方法,在線程第1次調用get()或set(Object)時才執行,并且僅執行1次。ThreadLocal中的缺省實現直接返回一個null。

  值得一提的是,在JDK5.0中,ThreadLocal已經支持泛型,該類的類名已經變為ThreadLocal<T>。API方法也相應進行了調整,新版本的API方法分別是void set(T value)、T get()以及T initialValue()。

  ThreadLocal是如何做到為每一個線程維護變量的副本的呢?其實實現的思路很簡單:在ThreadLocal類中有一個Map,用于存儲每一個線程的變量副本,Map中元素的鍵為線程對象,而值對應線程的變量副本。我們自己就可以提供一個簡單的實現版本:

[java] view plain copy print?在CODE上查看代碼片package com.test;    public class TestNum {      // ①通過匿名內部類覆蓋ThreadLocal的initialValue()方法,指定初始值      private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>() {          public Integer initialValue() {              return 0;          }      };        // ②獲取下一個序列值      public int getNextNum() {          seqNum.set(seqNum.get() + 1);          return seqNum.get();      }        public static void main(String[] args) {          TestNum sn = new TestNum();          // ③ 3個線程共享sn,各自產生序列號          TestClient t1 = new TestClient(sn);          TestClient t2 = new TestClient(sn);          TestClient t3 = new TestClient(sn);          t1.start();          t2.start();          t3.start();      }        private static class TestClient extends Thread {          private TestNum sn;            public TestClient(TestNum sn) {              this.sn = sn;          }            public void run() {              for (int i = 0; i < 3; i++) {                  // ④每個線程打出3個序列值                  System.out.println("thread[" + Thread.currentThread().getName() + "] --> sn["                           + sn.getNextNum() + "]");              }          }      }  }  

 通常我們通過匿名內部類的方式定義ThreadLocal的子類,提供初始的變量值,如例子中①處所示。TestClient線程產生一組序列號,在③處,我們生成3個TestClient,它們共享同一個TestNum實例。運行以上代碼,在控制臺上輸出以下的結果:

thread[Thread-0]%20-->%20sn[1]thread[Thread-1]%20-->%20sn[1]thread[Thread-2]%20-->%20sn[1]thread[Thread-1]%20-->%20sn[2]thread[Thread-0]%20-->%20sn[2]thread[Thread-1]%20-->%20sn[3]thread[Thread-2]%20-->%20sn[2]thread[Thread-0]%20-->%20sn[3]thread[Thread-2]%20-->%20sn[3]

考察輸出的結果信息,我們發現每個線程所產生的序號雖然都共享同一個TestNum實例,但它們并沒有發生相互干擾的情況,而是各自產生獨立的序列號,這是因為我們通過ThreadLocal為每一個線程提供了單獨的副本。

Thread同步機制的比較  ThreadLocal和線程同步機制相比有什么優勢呢?ThreadLocal和線程同步機制都是為了解決多線程中相同變量的訪問沖突問題。

  在同步機制中,通過對象的鎖機制保證同一時間只有一個線程訪問變量。這時該變量是多個線程共享的,使用同步機制要求程序慎密地分析什么時候對變量進行讀寫,什么時候需要鎖定某個對象,什么時候釋放對象鎖等繁雜的問題,程序設計和編寫難度相對較大。

  而ThreadLocal則從另一個角度來解決多線程的并發訪問。ThreadLocal會為每一個線程提供一個獨立的變量副本,從而隔離了多個線程對數據的訪問沖突。因為每一個線程都擁有自己的變量副本,從而也就沒有必要對該變量進行同步了。ThreadLocal提供了線程安全的共享對象,在編寫多線程代碼時,可以把不安全的變量封裝進ThreadLocal。

  由于ThreadLocal中可以持有任何類型的對象,低版本JDK所提供的get()返回的是Object對象,需要強制類型轉換。但JDK%205.0通過泛型很好的解決了這個問題,在一定程度地簡化ThreadLocal的使用,代碼清單%209%202就使用了JDK%205.0新的ThreadLocal<T>版本。

  概括起來說,對于多線程資源共享的問題,同步機制采用了“以時間換空間”的方式,而ThreadLocal采用了“以空間換時間”的方式。前者僅提供一份變量,讓不同的線程排隊訪問,而后者為每一個線程都提供了一份變量,因此可以同時訪問而互不影響。

  spring使用ThreadLocal解決線程安全問題我們知道在一般情況下,只有無狀態的Bean才可以在多線程環境下共享,在Spring中,絕大部分Bean都可以聲明為singleton作用域。就是因為Spring對一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非線程安全狀態采用ThreadLocal進行處理,讓它們也成為線程安全的狀態,因為有狀態的Bean就可以在多線程中共享了。

  一般的Web應用劃分為展現層、服務層和持久層三個層次,在不同的層中編寫對應的邏輯,下層通過接口向上層開放功能調用。在一般情況下,從接收請求到返回響應所經過的所有程序調用都同屬于一個線程,如圖9?2所示:

  同一線程貫通三層這樣你就可以根據需要,將一些非線程安全的變量以ThreadLocal存放,在同一次請求響應的調用線程中,所有關聯的對象引用到的都是同一個變量。

  下面的實例能夠體現Spring對有狀態Bean的改造思路:

代碼清單3 TestDao:非線程安全

[java] view plain copy print?在CODE上查看代碼片package com.test;    import java.sql.Connection;  import java.sql.SQLException;  import java.sql.Statement;    public class TestDao {      private Connection conn;// ①一個非線程安全的變量        public void addTopic() throws SQLException {          Statement stat = conn.createStatement();// ②引用非線程安全變量          // …      }  }  

由于①處的conn是成員變量,因為addTopic()方法是非線程安全的,必須在使用時創建一個新TopicDao實例(非singleton)。下面使用ThreadLocal對conn這個非線程安全的“狀態”進行改造:

代碼清單4%20TestDao:線程安全

[java] view%20plain copy print?package com.test;    import java.sql.Connection;  import java.sql.SQLException;  import java.sql.Statement;    public class TestDaoNew {      // ①使用ThreadLocal保存Connection變量      private static ThreadLocal<Connection> connThreadLocal = new ThreadLocal<Connection>();        public static Connection getConnection() {          // ②如果connThreadLocal沒有本線程對應的Connection創建一個新的Connection,          // 并將其保存到線程本地變量中。          if (connThreadLocal.get() == null) {              Connection conn = getConnection();              connThreadLocal.set(conn);              return conn;          } else {              return connThreadLocal.get();// ③直接返回線程本地變量          }      }        public void addTopic() throws SQLException {          // ④從ThreadLocal中獲取線程對應的Connection          Statement stat = getConnection().createStatement();      }  }  

  不同的線程在使用TopicDao時,先判斷connThreadLocal.get()是否是null,如果是null,則說明當前線程還沒有對應的Connection對象,這時創建一個Connection對象并添加到本地線程變量中;如果不為null,則說明當前的線程已經擁有了Connection對象,直接使用就可以了。這樣,就保證了不同的線程使用線程相關的Connection,而不會使用其它線程的Connection。因此,這個TopicDao就可以做到singleton共享了。

  當然,這個例子本身很粗糙,將Connection的ThreadLocal直接放在DAO只能做到本DAO的多個方法共享Connection時不發生線程安全問題,但無法和其它DAO共用同一個Connection,要做到同一事務多DAO共享同一Connection,必須在一個共同的外部類使用ThreadLocal保存Connection。

ConnectionManager.java

[java] view%20plain copy print?package com.test;    import java.sql.Connection;  import java.sql.DriverManager;  import java.sql.SQLException;    public class ConnectionManager {        private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {          @Override          protected Connection initialValue() {              Connection conn = null;              try {                  conn = DriverManager.getConnection(                          "jdbc:MySQL://localhost:3306/test", "username",                          "passWord");              } catch (SQLException e) {                  e.printStackTrace();              }              return conn;          }      };        public static Connection getConnection() {          return connectionHolder.get();      }        public static void setConnection(Connection conn) {          connectionHolder.set(conn);      }  }  

java.lang.ThreadLocal<T>的具體實現那么到底ThreadLocal類是如何實現這種“為每個線程提供不同的變量拷貝”的呢?先來看一下ThreadLocal的set()方法的/**     * Sets the current thread's copy of this thread-local variable     * to the specified value.  Most subclasses will have no need to     * override this method, relying solely on the {@link #initialValue}     * method to set the values of thread-locals.     *     * @param value the value to be stored in the current thread's copy of     *        this thread-local.     */     public void set(T value) {         Thread t = Thread.currentThread();         ThreadLocalMap map = getMap(t);         if (map != null)             map.set(this, value);         else             createMap(t, value);     }  在這個方法內部我們看到,首先通過getMap(Thread%20t)方法獲取一個和當前線程相關的ThreadLocalMap,然后將變量的值設置到這個ThreadLocalMap對象中,當然如果獲取到的ThreadLocalMap對象為空,就通過createMap方法創建。線程隔離的秘密,就在于ThreadLocalMap這個類。ThreadLocalMap是ThreadLocal類的一個靜態內部類,它實現了鍵值對的設置和獲?。▽Ρ萂ap對象來理解),每個線程中都有一個獨立的ThreadLocalMap副本,它所存儲的值,只能被當前線程讀取和修改。ThreadLocal類通過操作每一個線程特有的ThreadLocalMap副本,從而實現了變量訪問在不同線程中的隔離。因為每個線程的變量都是自己特有的,完全不會有并發錯誤。還有一點就是,ThreadLocalMap存儲的鍵值對中的鍵是this對象指向的ThreadLocal對象,而值就是你所設置的對象了。為了加深理解,我們接著看上面代碼中出現的getMap和createMap方法的實現:

[java] view%20plain copy print?/**  * Get the map associated with a ThreadLocal. Overridden in  * InheritableThreadLocal.  *  * @param  t the current thread  * @return the map  */  ThreadLocalMap getMap(Thread t) {      return t.threadLocals;  }    /**  * Create the map associated with a ThreadLocal. Overridden in  * InheritableThreadLocal.  *  * @param t the current thread  * @param firstValue value for the initial entry of the map  * @param map the map to store.  */  void createMap(Thread t, T firstValue) {      t.threadLocals = new ThreadLocalMap(this, firstValue);  }  接下來再看一下ThreadLocal類中的get()方法:

[java] view%20plain copy print?/**  * 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()方法:

[java] view%20plain copy print?/**     * Variant of set() to establish initialValue. Used instead     * of set() in case user has overridden the set() method.     *     * @return the initial value     */     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;     }    獲取和當前線程綁定的值時,ThreadLocalMap對象是以this指向的ThreadLocal對象為鍵進行查找的,這當然和前面set()方法的代碼是相呼應的?! ∵M一步地,我們可以創建不同的ThreadLocal實例來實現多個變量在不同線程間的訪問隔離,為什么可以這么做?因為不同的ThreadLocal對象作為不同鍵,當然也可以在線程的ThreadLocalMap對象中設置不同的值了。通過ThreadLocal對象,在多線程中共享一個值和多個值的區別,就像你在一個HashMap對象中存儲一個鍵值對和多個鍵值對一樣,僅此而已。

小結  ThreadLocal是解決線程安全問題一個很好的思路,它通過為每個線程提供一個獨立的變量副本解決了變量并發訪問的沖突問題。在很多情況下,ThreadLocal比直接使用synchronized同步機制解決線程安全問題更簡單,更方便,且結果程序擁有更高的并發性。

ConnectionManager.java

[java] view%20plain copy print?package com.test;    import java.sql.Connection;  import java.sql.DriverManager;  import java.sql.SQLException;    public class ConnectionManager {        private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {          @Override          protected Connection initialValue() {              Connection conn = null;              try {                  conn = DriverManager.getConnection(                          "jdbc:mysql://localhost:3306/test", "username",                          "password");              } catch (SQLException e) {                  e.printStackTrace();              }              return conn;          }      };        public static Connection getConnection() {          return connectionHolder.get();      }        public static void setConnection(Connection conn) {          connectionHolder.set(conn);      }  }  

后記  看到網友評論的很激烈,甚至關于ThreadLocalMap不是ThreadLocal里面的,而是Thread里面的這種評論都出現了,于是有了這個后記,下面先把jdk源碼貼上,源碼最有說服力了。

[java] view%20plain copy print?/**      * ThreadLocalMap is a customized hash map suitable only for      * maintaining thread local values. No Operations are exported      * outside of the ThreadLocal class. The class is package private to      * allow declaration of fields in class Thread.  To help deal with      * very large and long-lived usages, the hash table entries use      * WeakReferences for keys. However, since reference queues are not      * used, stale entries are guaranteed to be removed only when      * the table starts running out of space.      */      static class ThreadLocalMap {...}    源碼就是以上,這源碼自然是在ThreadLocal里面的,有截圖為證。

  本文是自己在學習ThreadLocal的時候,一時興起,深入看了源碼,思考了此類的作用、使用范圍,進而聯想到對傳統的synchronize共享變量線程安全的問題進行比較,而總結的博文,總結一句話就是一個是鎖機制進行時間換空間,一個是存儲拷貝進行空間換時間。

(全文完)


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
国产久一一精品| 国产脚交av在线一区二区| 亚洲欧美国产一本综合首页| 91网站在线看| 日本三级久久久| www.久久久久| 欧美极品少妇xxxxx| 在线播放国产一区中文字幕剧情欧美| 96精品久久久久中文字幕| 久久精品电影网| 色偷偷91综合久久噜噜| 在线观看成人黄色| 日韩美女av在线免费观看| 国产精品高清网站| 欧美伦理91i| 另类专区欧美制服同性| 国产精品久久久久91| 国产乱人伦真实精品视频| 久久亚洲私人国产精品va| 色樱桃影院亚洲精品影院| 欧美精品aaa| 欧美性jizz18性欧美| 亚洲自拍高清视频网站| 色噜噜亚洲精品中文字幕| 成人a在线观看| 欧美一性一乱一交一视频| 久久久免费观看视频| 国产精品成人一区二区三区吃奶| 亚洲精品中文字幕女同| 国产精品久久久久久婷婷天堂| 亚洲成人av片| 在线播放精品一区二区三区| 久久中国妇女中文字幕| 91精品国产乱码久久久久久蜜臀| 精品激情国产视频| 亚洲3p在线观看| 亚洲欧美日韩天堂| 国产欧美日韩中文| 色噜噜亚洲精品中文字幕| 日韩高清免费观看| 欧美日韩一区二区三区| 亚洲乱码国产乱码精品精天堂| 国产精品女人久久久久久| 亚洲精品免费一区二区三区| 国产91成人在在线播放| 成人xxxxx| 午夜精品在线视频| 国产精品黄色影片导航在线观看| 久久精品国产亚洲一区二区| 正在播放亚洲1区| 欧美在线观看www| 成人免费视频97| 欧美激情欧美激情在线五月| 国产精品视频免费在线| 97精品免费视频| 55夜色66夜色国产精品视频| 亚洲国产日韩欧美在线图片| 欧美性猛交99久久久久99按摩| 伊人久久久久久久久久久| 欧美精品久久久久久久免费观看| 日韩电影中文字幕在线观看| 在线视频日韩精品| 中文字幕日韩欧美精品在线观看| 精品久久久久久久中文字幕| 久久天天躁夜夜躁狠狠躁2022| 精品国偷自产在线视频99| 91精品啪在线观看麻豆免费| 国产精品香蕉国产| 91高潮精品免费porn| 97视频在线观看视频免费视频| 日韩精品极品毛片系列视频| 97在线观看免费| 国产在线播放91| 日韩一区二区久久久| 亚洲国产福利在线| 日本国产欧美一区二区三区| 国产精品久久久久久久久久免费| 欧美日韩国产综合新一区| 精品日韩视频在线观看| 亚洲欧美日韩天堂一区二区| 性色av一区二区三区红粉影视| 亚洲欧洲高清在线| 成人午夜高潮视频| 亚洲欧洲偷拍精品| 久久久久久久久电影| 日韩亚洲一区二区| 国产91精品网站| 精品久久久久人成| 韩曰欧美视频免费观看| 国产乱肥老妇国产一区二| 成人国产亚洲精品a区天堂华泰| 亚洲最大福利视频| 国产精品久久久久av| 亚洲一级免费视频| 这里只有精品视频| 欧美色播在线播放| 国产91色在线播放| 欧美最猛黑人xxxx黑人猛叫黄| 日韩av中文字幕在线免费观看| 日韩在线资源网| 国产美女直播视频一区| 午夜精品久久久久久久99热浪潮| 日本aⅴ大伊香蕉精品视频| 在线免费看av不卡| 欧美性受xxxx黑人猛交| 精品久久久久久中文字幕大豆网| 久久亚洲一区二区三区四区五区高| 精品国产91久久久久久老师| 国产成人免费av电影| 亚洲视频在线观看免费| 欧美大人香蕉在线| 亚洲图片欧美午夜| 国产极品精品在线观看| 大量国产精品视频| 国产精品xxx视频| 91av在线免费观看视频| 国产一区二区三区毛片| 有码中文亚洲精品| 91精品视频观看| 精品国产一区二区三区四区在线观看| 国产亚洲精品美女久久久| 草民午夜欧美限制a级福利片| 91久久精品国产91久久| 亚洲国产一区二区三区四区| 久久精品2019中文字幕| 色综合男人天堂| 国产精品网址在线| 午夜精品久久久久久99热软件| 久久91亚洲人成电影网站| 中文字幕精品久久久久| 国产精品电影网| 91亚洲永久免费精品| 国产精品久久99久久| 久久久精品在线观看| 国产一区二区激情| 2019中文字幕在线| 日韩av免费在线看| 亚洲人成电影网站色…| 国产亚洲精品久久久| 日韩不卡在线观看| 久久视频这里只有精品| 欧美大尺度激情区在线播放| 尤物99国产成人精品视频| 最近2019年手机中文字幕| 日韩亚洲欧美中文高清在线| 中文字幕久热精品在线视频| 中文字幕av一区| 91精品国产乱码久久久久久久久| 欧美性精品220| 亚洲午夜av久久乱码| 另类美女黄大片| 国产精品美女久久| 免费av一区二区| 91国产美女视频| 日韩最新在线视频| 亚洲日本成人女熟在线观看| 美女性感视频久久久| 97在线观看视频国产| 91精品国产综合久久男男| 日韩性xxxx爱| 91国偷自产一区二区三区的观看方式| www国产精品com| 日本a级片电影一区二区| 中文字幕免费精品一区高清|