既然寫了博客我就寫的詳細一些,儘量易懂一些(反正我也操作了,哈哈),以後慢慢的就不會了呦
來上才藝了哈:學習、認識三部曲:
1. ThreadLocal是什麼
2. ThreadLocal的應用
3. ThreadLocal怎麼實現的
4. ThreadLocal有沒有問題(內存泄漏)
5. 融會貫通
那我們開始吧
ThreadLocal是什麼
當程序每新啓動一個線程(web應用上包括用戶訪問),ThreadLocal會依據你當前的線程給你保存一些信息,僅僅是和你當前線程相關。
ThreadLocal的應用
既然可以攜帶和你當前線程有關的信息,那是不是我可以把每個訪問線程都放到裏面,就方便存取了呢,哪些應用可以實現(之前接觸的有放入用戶信息的),做的最多的可能就是去操作數據,裏面放的都connection信息,這樣操作就相當於每個線程只建立一個連接。給你個示例代碼自己先看看
public class DBUtil {
// 數據庫配置
private static final String driver = "com.mysql.jdbc.Driver";
private static final String url = "jdbc:mysql://localhost:3306/demo";
private static final String username = "root";
private static final String password = "root";
// 定義一個用於放置數據庫連接的局部線程變量(使每個線程都擁有自己的連接)
private static ThreadLocal<Connection> connContainer = new ThreadLocal<Connection>();
// 獲取連接
public static Connection getConnection() {
Connection conn = connContainer.get();
try {
if (conn == null) {
Class.forName(driver);
conn = DriverManager.getConnection(url, username, password);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
connContainer.set(conn);
}
return conn;
}
// 關閉連接
public static void closeConnection() {
Connection conn = connContainer.get();
try {
if (conn != null) {
conn.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
connContainer.remove();
}
}
}
ThreadLocal怎麼實現的
既然已經知道獲取的是當前線程相關的那就解析一下這個過程。先上類圖:
應該先看 Thread 裏面:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
threadLocals 是一個map 沒錯就是它,它保存的是你的當前線程和你對應的value,對應的
ThreadLocalMap 是ThreadLocal的內部類。這個就說明了,我把它交給了ThreadLocal去管理。
那現在就得看它的初始化了,一開始它做了什麼:
/**
* 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;
}
/**
* 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;
}
當然了有些會重寫它的初始化,在獲取的時候,這樣就可以直接賦值了。
看到了吧 threadLocals 的key 就是 當前的ThreadLocal。不行再看下set 和 get 哈
/**
* 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);
}
我已經盡力了。
簡單的說就是:
Thread.threadLocals(map) ------>ThreadLocal.ThreadLocalMap(ThreadLocal爲鍵,注意ThreadLocalMap是個靜態class)------>set、get 調用初始化賦值
ThreadLocal有沒有問題(內存泄漏)
Thread---->ThreadLocal.ThreadLocalMap
其實還是map中的內存泄漏問題。map中把theadlocal 變成null,GC之後在map中存儲的弱引用對象的hashcode來作爲key,那麼它就被回收了,但是它的value一直就被放在堆裏面,並且被當前的線程threadLocals引用,因爲在線程池裏的他可能一直都不釋放,導致內存泄漏了。 所以導致內存泄漏的情況需要滿足:
- 觸發了垃圾回收
- 沒有調用get、set、remove因爲如果調用這個threadlocal 會發現鍵爲空了,會重新賦值
- 線程一直運行
所以解決方案:
最簡單有效的方法是使用後將其移除 記得用remove
過程大家可以參考這個我覺得還沒他寫的好
ThreadLocal理解及應用
我們來分析一下這個mat圖:
這個視圖呢,是分析存活的最大空間佔用的,在每一列的頭部可以正則搜索,我們搜索
這個是分析對象實例個數和佔用的
這個工具還可以分析多個dump文件來進行對比分析
JVM故障分析及性能優化
然後搜索發現沒有key的引用,只有value的空間佔用很大。
驗證了內存泄漏的問題