ThreadLocal的介紹與運用( 四 )

? (2) AccountService類的修改:不需要傳遞connection對象
package com.itheima.transfer.service;import com.itheima.transfer.dao.AccountDao;import com.itheima.transfer.utils.JdbcUtils;import java.sql.Connection;public class AccountService {public boolean transfer(String outUser, String inUser, int money) {AccountDao ad = new AccountDao();try {Connection conn = JdbcUtils.getConnection();//開啟事務conn.setAutoCommit(false);// 轉出 : 這里不需要傳參了 !ad.out(outUser, money);// 模擬轉賬過程中的異常//int i = 1 / 0;// 轉入ad.in(inUser, money);//事務提交JdbcUtils.commitAndClose();} catch (Exception e) {e.printStackTrace();//事務回滾JdbcUtils.rollbackAndClose();return false;}return true;}}? (3) AccountDao類的修改:照常使用
package com.itheima.transfer.dao;import com.itheima.transfer.utils.JdbcUtils;import java.sql.Connection;import java.sql.PreparedStatement;import java.sql.SQLException;public class AccountDao {public void out(String outUser, int money) throws SQLException {String sql = "update account set money = money - ? where name = ?";Connection conn = JdbcUtils.getConnection();PreparedStatement pstm = conn.prepareStatement(sql);pstm.setInt(1,money);pstm.setString(2,outUser);pstm.executeUpdate();//照常使用//JdbcUtils.release(pstm,conn);JdbcUtils.release(pstm);}public void in(String inUser, int money) throws SQLException {String sql = "update account set money = money + ? where name = ?";Connection conn = JdbcUtils.getConnection();PreparedStatement pstm = conn.prepareStatement(sql);pstm.setInt(1,money);pstm.setString(2,inUser);pstm.executeUpdate();//JdbcUtils.release(pstm,conn);JdbcUtils.release(pstm);}}2.3.2 ThreadLocal方案的好處從上述的案例中我們可以看到,在一些特定場景下,ThreadLocal方案有兩個突出的優勢:

  1. 傳遞數據 : 保存每個線程綁定的數據,在需要的地方可以直接獲取, 避免參數直接傳遞帶來的代碼耦合問題
  2. 線程隔離 : 各線程之間的數據相互隔離卻又具備并發性,避免同步方式帶來的性能損失
3. ThreadLocal的內部結構? 通過以上的學習,我們對ThreadLocal的作用有了一定的認識 ?,F在我們一起來看一下ThreadLocal的內部結構,探究它能夠實現線程數據隔離的原理 。
3.1常見的誤解? 通常,如果我們不去看源代碼的話,我猜ThreadLocal是這樣子設計的:每個ThreadLocal類都創建一個Map,然后用線程的ID threadID作為Mapkey,要存儲的局部變量作為Mapvalue , 這樣就能達到各個線程的局部變量隔離的效果 。這是最簡單的設計方法,JDK最早期的ThreadLocal就是這樣設計的 。
3.2核心結構? 但是,JDK后面優化了設計方案,現時JDK8 ThreadLocal的設計是:每個Thread維護一個ThreadLocalMap哈希表,這個哈希表的keyThreadLocal實例本身,value才是真正要存儲的值Object 。
? (1) 每個Thread線程內部都有一個Map (ThreadLocalMap)? (2) Map里面存儲ThreadLocal對象(key)和線程的變量副本(value)? (3)Thread內部的Map是由ThreadLocal維護的,由ThreadLocal負責向map獲取和設置線程的變量值 。? (4)對于不同的線程,每次獲取副本值時,別的線程并不能獲取到當前線程的副本值,形成了副本的隔離,互不干擾 。
ThreadLocal的介紹與運用

文章插圖
3.3 這樣設計的好處? 這個設計與我們一開始說的設計剛好相反,這樣設計有如下兩個優勢:
(1) 這樣設計之后每個Map存儲的Entry數量就會變少,因為之前的存儲數量由Thread的數量決定 , 現在是由ThreadLocal的數量決定 。
(2) 當Thread銷毀之后,對應的ThreadLocalMap也會隨之銷毀,能減少內存的使用 。
4. ThreadLocal的核心方法源碼? 基于ThreadLocal的內部結構 , 我們繼續探究一下ThreadLocal的核心方法源碼,更深入的了解其操作原理 。
除了構造之外 ,  ThreadLocal對外暴露的方法有以下4個:
方法聲明描述protected T initialValue()返回當前線程局部變量的初始值public void set( T value)設置當前線程綁定的局部變量public T get()獲取當前線程綁定的局部變量public void remove()移除當前線程綁定的局部變量其實get,set和remove邏輯是比較相似的,我們要研究清楚其中一個,其他也就明白了 。
4.1 get方法(1 ) 源碼和對應的中文注釋
/*** 返回當前線程中保存ThreadLocal的值* 如果當前線程沒有此ThreadLocal變量,* 則它會通過調用{@link #initialValue} 方法進行初始化值** @return 返回當前線程對應此ThreadLocal的值*/public T get() {// 獲取當前線程對象Thread t = Thread.currentThread();// 獲取此線程對象中維護的ThreadLocalMap對象ThreadLocalMap map = getMap(t);// 如果此map存在if (map != null) {// 以當前的ThreadLocal 為 key , 調用getEntry獲取對應的存儲實體eThreadLocalMap.Entry e = map.getEntry(this);// 找到對應的存儲實體 eif (e != null) {@SuppressWarnings("unchecked")// 獲取存儲實體 e 對應的 value值// 即為我們想要的當前線程對應此ThreadLocal的值T result = (T)e.value;return result;}}// 如果map不存在,則證明此線程沒有維護的ThreadLocalMap對象// 調用setInitialValue進行初始化return setInitialValue();}/*** set的變樣實現,用于初始化值initialValue,* 用于代替防止用戶重寫set()方法** @return the initial value 初始化后的值*/private T setInitialValue() {// 調用initialValue獲取初始化的值T value = https://www.huyubaike.com/biancheng/initialValue();// 獲取當前線程對象Thread t = Thread.currentThread();// 獲取此線程對象中維護的ThreadLocalMap對象ThreadLocalMap map = getMap(t);// 如果此map存在if (map != null)// 存在則調用map.set設置此實體entrymap.set(this, value);else// 1)當前線程Thread 不存在ThreadLocalMap對象// 2)則調用createMap進行ThreadLocalMap對象的初始化// 3)并將此實體entry作為第一個值存放至ThreadLocalMap中createMap(t, value);// 返回設置的值valuereturn value;}/*** 獲取當前線程Thread對應維護的ThreadLocalMap** @paramt the current thread 當前線程* @return the map 對應維護的ThreadLocalMap*/ThreadLocalMap getMap(Thread t) {return t.threadLocals;} /***創建當前線程Thread對應維護的ThreadLocalMap** @param t 當前線程* @param firstValue 存放到map中第一個entry的值*/ void createMap(Thread t, T firstValue) {//這里的this是調用此方法的threadLocalt.threadLocals = new ThreadLocalMap(this, firstValue);}

推薦閱讀