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