Java并發編程--理解ThreadLocal

另一篇博文:Hibernet中的ThreadLocal使用 http://www.cnblogs.com/gnivor/p/4440776.html

本文參考:
http://blog.csdn.net/lufeng20/article/details/24314381
http://www.cnblogs.com/chenying99/articles/3405161.html

??

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。

?

一、知其然

synchronized這類線程同步的機制可以解決多線程并發問題,在這種解決方案下,多個線程訪問到的,都是同一份變量的內容。為了防止在多線程訪問的過程中,可能會出現的并發錯誤。不得不對多個線程的訪問進行同步,這樣也就意味著,多個線程必須先后對變量的值進行訪問或者修改,這是一種以延長訪問時間來換取線程安全性的策略。

而ThreadLocal類為每一個線程都維護了自己獨有的變量拷貝。每個線程都擁有了自己獨立的一個變量,競爭條件被徹底消除了,那就沒有任何必要對這些線程進行同步,它們也能最大限度的由CPU調度,并發執行。并且由于每個線程在訪問該變量時,讀取和修改的,都是自己獨有的那一份變量拷貝,變量被徹底封閉在每個訪問的線程中,并發錯誤出現的可能也完全消除了。對比前一種方案,這是一種以空間來換取線程安全性的策略。

來看一個運用ThreadLocal來實現數據庫連接Connection對象線程隔離的例子。

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);  }  
}  

  通過調用ConnectionManager.getConnection()方法,每個線程獲取到的,都是和當前線程綁定的那個Connection對象,第一次獲取時,是通過initialValue()方法的返回值來設置值的。通過ConnectionManager.setConnection(Connection conn)方法設置的Connection對象,也只會和當前線程綁定。這樣就實現了Connection對象在多個線程中的完全隔離。在Spring容器中管理多線程環境下的Connection對象時,采用的思路和以上代碼非常相似。

附:另一個例子?

public class TestNum {    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();        //三個線程共享SN 產生序列號ThreadClient t1 = new ThreadClient(sn);ThreadClient t2 = new ThreadClient(sn);ThreadClient t3 = new ThreadClient(sn);t1.start();t2.start();t3.start();}
}class ThreadClient extends Thread{private TestNum sn ;public ThreadClient(TestNum sn){this.sn = sn;}public void run(){for(int i = 0 ; i < 3 ; i++){System.out.println("Thread: "+ Thread.currentThread().getName() + " sn: " + sn.getNextNum());}}    
}
View Code

??

二、知其所以然

那么到底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 t)方法獲取一個和當前線程相關的ThreadLocalMap,然后將變量的值設置到這個ThreadLocalMap對象中,當然如果獲取到的ThreadLocalMap對象為空,就通過createMap方法創建。

  線程隔離的秘密,就在于ThreadLocalMap這個類。ThreadLocalMap是ThreadLocal類的一個靜態內部類,它實現了鍵值對的設置和獲取(對比Map對象來理解),每個線程中都有一個獨立的ThreadLocalMap副本,它所存儲的值,只能被當前線程讀取和修改。ThreadLocal類通過操作每一個線程特有的ThreadLocalMap副本,從而實現了變量訪問在不同線程中的隔離。因為每個線程的變量都是自己特有的,完全不會有并發錯誤。還有一點就是,ThreadLocalMap存儲的鍵值對中的鍵是this對象指向的ThreadLocal對象,而值就是你所設置的對象了。

為了加深理解,我們接著看上面代碼中出現的getMapcreateMap方法的實現:?

/** * 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);  
}  

?代碼已經說的非常直白,就是獲取和設置Thread內的一個叫threadLocals的變量,而這個變量的類型就是ThreadLocalMap,這樣進一步驗證了上文中的觀點:每個線程都有自己獨立的ThreadLocalMap對象。打開java.lang.Thread類的源代碼,我們能得到更直觀的證明:

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

?

那么接下來再看一下ThreadLocal類中的get()方法,代碼是這么說的:?

/** * 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()方法:?

/** * 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()方法的代碼是相呼應的。

進一步地,我們可以創建不同的ThreadLocal實例來實現多個變量在不同線程間的訪問隔離,為什么可以這么做?因為不同的ThreadLocal對象作為不同鍵,當然也可以在線程的ThreadLocalMap對象中設置不同的值了。通過ThreadLocal對象,在多線程中共享一個值和多個值的區別,就像你在一個HashMap對象中存儲一個鍵值對和多個鍵值對一樣,僅此而已。

設置到這些線程中的隔離變量,會不會導致內存泄漏呢?ThreadLocalMap對象保存在Thread對象中,當某個線程終止后,存儲在其中的線程隔離的變量,也將作為Thread實例的垃圾被回收掉,所以完全不用擔心內存泄漏的問題。在多個線程中隔離的變量,光榮的生,合理的死,真是圓滿,不是么?

最后再提一句,ThreadLocal變量的這種隔離策略,也不是任何情況下都能使用的。如果多個線程并發訪問的對象實例只允許,也只能創建一個,那就沒有別的辦法了,老老實實的使用同步機制(synchronized)來訪問吧。

http://my.oschina.net/lichhao/blog/111362

轉載于:https://www.cnblogs.com/gnivor/p/4904793.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/397379.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/397379.shtml
英文地址,請注明出處:http://en.pswp.cn/news/397379.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

delphi Post數據到網頁

varhttp: TIdHttp;sendtoserver: TStringStream;str: string; beginhttp : TIdHttp.Create(); // 創建http.HandleRedirects : True; // 允許轉頭http.ReadTimeout : 3000; …

python之路——迭代器與生成器

要了解for循環是怎么回事兒&#xff0c;咱們還是要從代碼的角度出發。 首先&#xff0c;我們對一個列表進行for循環。 for i in [1,2,3,4]: print(i) 上面這段代碼肯定是沒有問題的&#xff0c;但是我們換一種情況&#xff0c;來循環一個數字1234試試 for i in 1234print(i) 結…

HTML頁面顯示透視效果,html – CSS – 對背景圖像的“敲除”/透視效果

我認為這里的想法是圖像必須足夠大,以覆蓋網頁或至少父母div ..然后,您可以將圖像應用于容器和’inner’div的背景.覆蓋可以通過偽元素而不是單獨的div來實現.修訂結構 –.bck {position: relative;height: 800px;width: 100%;background:url(http://webneel.com/wallpaper/sit…

DFS分布式文件系統--管理篇

DFS分布式文件系統--管理篇參考文檔&#xff1a;淺談DFS分布式文件系統DFS 命名空間 和 DFS 復制概述續DFS分布式文件系統--基礎篇DFS分布式文件系統--部署篇添加命名空間服務器&#xff08;添加第二臺命名空間服務器 NameSrv02)成功后如下圖&#xff1a;“從顯示區域隱藏命名空…

Linux 0-1 修改主機名及IP地址

1.修改主機名 hostname 查看主機名 vi /etc/sysconfig/network 修改hostname主機名 vi /etc/hosts 修改127.0.1 主機名 service network restart #/etc/hosts 在域名解析時優先于DNS服務器2.IP地址 ifconfig 查看目前網絡卡信息 cd /etc/sysconfig/network-scripts ls查看…

html漸變顏色代碼表,漸變顏色代碼表

漸變顏色代碼表2020-12-24素材&#xff1a;網絡 編輯&#xff1a;唔爾灬#000000#2F0000#600030#460046#28004D#272727#4D0000#820041#5E005E#3A006F#3C3C3C#600000#9F0050#750075#4B0091#4F4F4F#750000#BF0060#930093#5B00AE#5B5B5B#930000#D9006C#AE00AE#6F00D2#6C6C6C#AE0000…

js貪心算法---背包問題

/** param {Object} capacity 背包容量 6* param {Object} weights 物品重量 [2,3,4]* param {Object} values 物品價值 [3,4,5]*///貪心算法&#xff0c;只能算&#xff0c;可以分割的物品&#xff0c;如果不能分割物品&#xff0c;只能得到近似解&#xff0c;不分割物品&…

Spring利用JDBCTemplate實現批量插入和返回id

1、先介紹一下java.sql.Connection接口提供的三個在執行插入語句后可取的自動生成的主鍵的方法&#xff1a; //第一個是 PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException; 其中autoGenerateKeys 有兩個可選值&#xff1a;Stat…

jsp壓縮html,使用HtmlCompressor壓縮JSP編譯的Html代碼

HtmlCompressor 能夠刪除多余的HTML代碼。它提供多種方法&#xff1a;刪除無用的空行、刪除注釋以及刪除無用的表格等等&#xff0c;簡單而有效。在Java代碼中可以這樣使用&#xff1a;String html getHtml(); //需要處理的Html代碼HtmlCompressor compressor new HtmlCompre…

LVS負載均衡(3)——LVS工作模式與工作原理

LVS介紹及工作原理1. LVS 介紹LVS,Linux Virtual Server 的簡寫&#xff0c;意即 Linux 虛擬服務器&#xff0c;是一個虛擬的服務器集群系統&#xff0c;可以在 UNIX/Linux 平臺下實現負載均衡集群功能。文章&#xff1a;LVS項目介紹LVS集群體系結構LVS集群的IP負載均衡技術LVS…

保留凸性的方式:一個凸函數在一個隨機變量上的期望仍然是凸函數

設函數 gg 是實數范圍內的一個凸函數&#xff0c;DD 是一個隨機變量&#xff0c; 那么函數 GEDg(y?D)GEDg(y?D) 仍然是一個凸函數。 證明&#xff1a;記 θθθθ, yy 與 yy 是任意兩個數 ≥θG(y)θG(y)θEDg(y?D)θEDg(y?D)ED[θg(y?D)θ(gy?D)]ED[g(θyθy?D)]G(θyθ…

MyBatis入門(二)---一對一,一對多

一、創建數據庫表 1.1、創建數據表同時插入數據 /* SQLyog Enterprise v12.09 (64 bit) MySQL - 5.6.27-log : Database - mybatis ********************************************************************* *//*!40101 SET NAMES utf8 */;/*!40101 SET SQL_MODE*/;/*!40014 SE…

零基礎學Java的10個方法

2019獨角獸企業重金招聘Python工程師標準>>> 版權聲明&#xff1a;本文為北京尚學堂原創文章&#xff0c;未經允許不得轉載。? 零基礎學Java只要方法得當&#xff0c;依然有機會學習好Java編程。 但作為初學者可以通過制定一些合理清晰的學習計劃。 在幫你屢清楚思…

html 轉換為cshtml,使用Html而不是csHtml

我想使用純HTML頁面而不是使用MVC .net的cshtml . 但是當我通過右鍵單擊索引添加視圖時&#xff0c;我只能看到兩個選項 .public class HomeController : Controller{//// GET: /Home/public ActionResult Index(){return View();}}Cshtml(剃刀)Aspx論壇但仍無濟于事 . 我仍然沒…

scp windows 和 linux 遠程復制 (雙向)

一下命令在cmd中 從w -> l : scp D:\a.txt root192.168.2.113:/home/a 從l -> w: scp root192.168.2.113:/home/aaa d:/b.txt 按說在Linux中也可以&#xff0c;但是不知道怎么的只有在winodws上行&#xff0c;在linux上就會報 ssh: connect to host 192.168.2.157 port 2…

北京尚學堂|程序員的智慧

2019獨角獸企業重金招聘Python工程師標準>>> 版權聲明&#xff1a;本文為北京尚學堂原創文章&#xff0c;未經允許不得轉載。 編程是一種創造性的工作&#xff0c;是一門藝術。精通任何一門藝術&#xff0c;都需要很多的練習和領悟&#xff0c;所以這里提出的“智慧…

翼城中學2021高考成績查詢入口,2021年臨汾中考分數線查詢(4)

臨汾2021年中考分數線查詢 2021臨汾中考錄取分數線 19年臨汾中考各校錄取分數線 臨汾各高中錄取分數線 臨汾2021中考錄取線查詢 中考信息網提供2021臨汾中考分數線查詢信息。臨汾中考錄取分數線預計7月初公布&#xff0c;屆時考生可登陸臨汾招生考試網官網查看分數線情況。2…

JSP EL表達式 param、paramValues的使用

JSP EL表達式 param、paramValues的使用&#xff1a; <% page language"java" import"java.util.*" pageEncoding"UTF-8"%> <%String path request.getContextPath();String basePath request.getScheme() "://" request…

配置Tomcat使用HTTP/2

轉自&#xff1a; https://zhuanlan.zhihu.com/p/21349186 前情提要&#xff1a; Tomcat高效響應的秘密(一) Sendfile與Gzip Tomcat高效響應的秘密(二) keep alive 前面高效響應的兩篇&#xff0c;我們分析了Sendfile的特性以及HTTP1.1的keep-alive特性&#xff0c;基于這些功…

asp.net razor html,從控制臺應用程序中的ASP.NET Razor模板生成HTML的當前最佳解決方案是什么?...

ServiceStack是用于呈現Razor視圖頁面的另一個選項。 盡管它已針對集成到ASP.NET或HttpListener Web Host中進行了優化(并提供了用于在目錄中自動發現和注冊視圖頁面&#xff0c;即時重新編譯修改后的頁面等的API)&#xff0c;但它還支持靜態生成視圖頁面 &#xff1a;var razo…