摘要:它為每個(gè)線程維護(hù)了一個(gè)自己的連接,并且可以在線程內(nèi)共享。當(dāng)數(shù)組較大時(shí),這個(gè)性能會(huì)很差,所以建議盡量控制的數(shù)量。
使用場(chǎng)景
假設(shè)我們有一個(gè)數(shù)據(jù)庫(kù)連接管理類:
class ConnectionManager { private static Connection connect = null; private static String url = System.getProperty("URL"); public static Connection openConnection() { if(connect == null){ try { connect = DriverManager.getConnection(url); } catch (SQLException e) { e.printStackTrace(); } } return connect; } public static void closeConnection() { if(connect!=null) { try { connect.close(); } catch (SQLException e) { e.printStackTrace(); } } } }
如果這個(gè)類被用在多線程環(huán)境內(nèi),則會(huì)存在線程安全問題,那么可以對(duì)這兩個(gè)方法添加synchronized關(guān)鍵字進(jìn)行同步處理,不過這樣會(huì)大大降低程序的性能,也可以將connection變成局部變量:
class ConnectionManager { private Connection connect = null; public Connection openConnection(String url) { if(connect == null){ try { connect = DriverManager.getConnection(url); } catch (SQLException e) { e.printStackTrace(); } } return connect; } public void closeConnection() { if(connect!=null) { try { connect.close(); } catch (SQLException e) { e.printStackTrace(); } } } } class ConnectionManagerTest { private String url = System.getProperty("URL"); public void insert() { ConnectionManager connectionManager = new ConnectionManager(); Connection connection = connectionManager.openConnection(this.url); //使用connection進(jìn)行操作 connectionManager.closeConnection(); } public void update() { ConnectionManager connectionManager = new ConnectionManager(); Connection connection = connectionManager.openConnection(this.url); //使用connection進(jìn)行操作 connectionManager.closeConnection(); } }
每個(gè)CURD方法都創(chuàng)建新的數(shù)據(jù)庫(kù)連接會(huì)造成數(shù)據(jù)庫(kù)的很大壓力,這里可以有兩種解決方案:
使用連接池管理連接,既不是每次都創(chuàng)建、銷毀連接,而是從一個(gè)連接池里借出可用的連接,用完將其歸還。參加MyBatis連接管理(1) | MyBatis連接管理(2)
可以看到,這里connection的建立最好是這樣的:每個(gè)線程希望有自己獨(dú)立的連接來避免同步問題,在線程內(nèi)部希望共用同一個(gè)連接來降低數(shù)據(jù)庫(kù)的壓力,那么使用ThreadLocal來管理數(shù)據(jù)庫(kù)連接就是最好的選擇了。它為每個(gè)線程維護(hù)了一個(gè)自己的連接,并且可以在線程內(nèi)共享。
class ConnectionManager { private static String url = System.getProperty("URL"); private static ThreadLocalconnectionHolder = ThreadLocal.withInitial(() -> { try { return DriverManager.getConnection(url); } catch (SQLException e) { e.printStackTrace(); } return null; }); public static Connection openConnection() { return connectionHolder.get(); } public static void closeConnection() { Connection connect = connectionHolder.get(); if(connect!=null) { try { connect.close(); } catch (SQLException e) { e.printStackTrace(); } } } }
另外還可以用到其他需要每個(gè)線程管理一份自己的資源副本的地方:An Introduction to ThreadLocal in Java
實(shí)現(xiàn)原理這里面涉及到三種對(duì)象的映射:Thread-ThreadLocal對(duì)象-ThreadLocal中存的具體內(nèi)容,既然是每個(gè)線程都會(huì)有一個(gè)資源副本,那么這個(gè)從ThreadLocal對(duì)象到存儲(chǔ)內(nèi)容的映射自然就會(huì)存在Thread對(duì)象里:
ThreadLocal.ThreadLocalMap threadLocals = null;
而ThreadLocal類只是提供了訪問這個(gè)Map的接口:
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); } public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
這個(gè)ThreadLocalMap是ThreadLocal的內(nèi)部類,實(shí)現(xiàn)了一個(gè)類似HashMap的功能,其內(nèi)部維護(hù)了一個(gè)Entry數(shù)組,下標(biāo)就是通過ThreadLocal對(duì)象的threadLocalHashCode計(jì)算得來。這個(gè)Entry繼承自WeakReference,實(shí)現(xiàn)對(duì)key,也就是ThreadLocal的弱引用:
static class Entry extends WeakReference> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal> k, Object v) { super(k); value = v; } }
內(nèi)存模型圖如下:
當(dāng)ThreadLocal Ref出棧后,由于ThreadLocalMap中Entry對(duì)ThreadLocal只是弱引用,所以ThreadLocal對(duì)象會(huì)被回收,Entry的key會(huì)變成null,然后在每次get/set/remove ThreadLocalMap中的值的時(shí)候,會(huì)自動(dòng)清理key為null的value,這樣value也能被回收了。
注意:
如果ThreadLocal Ref一直沒有出棧(例如上面的connectionHolder,通常我們需要保證ThreadLocal為單例且全局可訪問,所以設(shè)為static),具有跟Thread相同的生命周期,那么這里的虛引用便形同虛設(shè)了,所以使用完后記得調(diào)用ThreadLocal.remove將其對(duì)應(yīng)的value清除。
另外,由于ThreadLocalMap中只對(duì)ThreadLocal是弱引用,對(duì)value是強(qiáng)引用,如果ThreadLocal因?yàn)闆]有其他強(qiáng)引用而被回收,之后也沒有調(diào)用過get/set,那么就會(huì)產(chǎn)生內(nèi)存泄露,
在使用線程池時(shí),線程會(huì)被復(fù)用,那么里面保存的ThreadLocalMap同樣也會(huì)被復(fù)用,會(huì)造成線程之間的資源沒有被隔離,所以在線程歸還回線程池時(shí)要記得調(diào)用remove方法。
hash沖突上面提到ThreadLocalMap是自己實(shí)現(xiàn)的類似HashMap的功能,當(dāng)出現(xiàn)Hash沖突(通過兩個(gè)key對(duì)象的hash值計(jì)算得到同一個(gè)數(shù)組下標(biāo))時(shí),它沒有采用鏈表模式,而是采用的線性探測(cè)的方法,既當(dāng)發(fā)生沖突后,就線性查找數(shù)組中空閑的位置。當(dāng)數(shù)組較大時(shí),這個(gè)性能會(huì)很差,所以建議盡量控制ThreadLocal的數(shù)量。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/77745.html
摘要:但是還有另外的功能看的后一半代碼作用就是掃描位置之后的數(shù)組直到某一個(gè)為的位置,清除每個(gè)為的,所以使用可以降低內(nèi)存泄漏的概率。 在涉及到多線程需要共享變量的時(shí)候,一般有兩種方法:其一就是使用互斥鎖,使得在每個(gè)時(shí)刻只能有一個(gè)線程訪問該變量,好處就是便于編碼(直接使用 synchronized 關(guān)鍵字進(jìn)行同步訪問),缺點(diǎn)在于這增加了線程間的競(jìng)爭(zhēng),降低了效率;其二就是使用本文要講的 Threa...
摘要:那線程局部變量就是每個(gè)線程都會(huì)有一個(gè)局部變量,獨(dú)立于變量的初始化副本,而各個(gè)副本是通過線程唯一標(biāo)識(shí)相關(guān)聯(lián)的。移除此線程局部變量當(dāng)前線程的值。如果此線程局部變量隨后被當(dāng)前線程讀取,且這期間當(dāng)前線程沒有設(shè)置其值,則將調(diào)用其方法重新初始化其值。 前言 ThreadLocal網(wǎng)上資料很多,那我為什么還要寫下這篇文章呢?主要是想?yún)R聚多篇文章的優(yōu)秀之處以及我對(duì)于ThreadLocal的理解來加深印...
摘要:方法,刪除當(dāng)前線程綁定的這個(gè)副本數(shù)字,這個(gè)值是的值,普通的是使用鏈表來處理沖突的,但是是使用線性探測(cè)法來處理沖突的,就是每次增加的步長(zhǎng),根據(jù)參考資料所說,選擇這個(gè)數(shù)字是為了讓沖突概率最小。 showImg(https://segmentfault.com/img/remote/1460000019828633); 老套路,先列舉下關(guān)于ThreadLocal常見的疑問,希望可以通過這篇學(xué)...
摘要:在方法中取出開始時(shí)間,并計(jì)算耗時(shí)。是一個(gè)數(shù)組主要用來保存具體的數(shù)據(jù),是的大小,而這表示當(dāng)中元素?cái)?shù)量超過該值時(shí),就會(huì)擴(kuò)容。如果這個(gè)剛好就是當(dāng)前對(duì)象,則直接修改該位置上對(duì)象的。 想要獲取更多文章可以訪問我的博客?-?代碼無(wú)止境。 什么是ThreadLocal ThreadLocal在《Java核心技術(shù) 卷一》中被稱作線程局部變量(PS:關(guān)注公眾號(hào)itweknow,回復(fù)Java核心技術(shù)獲取該...
閱讀 2679·2021-11-24 09:38
閱讀 1991·2019-08-30 15:53
閱讀 1261·2019-08-30 15:44
閱讀 3242·2019-08-30 14:10
閱讀 3594·2019-08-29 16:29
閱讀 1813·2019-08-29 16:23
閱讀 1110·2019-08-29 16:20
閱讀 1482·2019-08-29 11:13