摘要:基本在項目開發中基本不會用到但是面試官是比較喜歡問這類問題的所以還是有必要了解一下該類的功能與原理的是什么是一個將在多線程中為每一個線程創建多帶帶的變量副本的類當使用來維護變量時會為每個線程創建多帶帶的變量副本避免因多線程操作共享變量而導致的數
ThreadLocal基本在項目開發中基本不會用到, 但是面試官是比較喜歡問這類問題的;所以還是有必要了解一下該類的功能與原理的.ThreadLocal是什么
ThreadLocal是一個將在多線程中為每一個線程創建多帶帶的變量副本的類; 當使用ThreadLocal來維護變量時, ThreadLocal會為每個線程創建多帶帶的變量副本, 避免因多線程操作共享變量而導致的數據不一致的情況;
ThreadLocal類用在哪些場景一般來說, ThreadLocal在實際工業生產中并不常見, 但是在很多框架中使用卻能夠解決一些框架問題; 比如Spring中的事務、Spring 中 作用域 Scope 為 Request的Bean 使用ThreadLocal來解決.
ThreadLocal使用方法1、將需要被多線程訪問的屬性使用ThreadLocal變量來定義; 下面以網上多數舉例的DBConnectionFactory類為例來舉例
import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBConnectionFactory { private static final ThreadLocaldbConnectionLocal = new ThreadLocal () { @Override protected Connection initialValue() { try { return DriverManager.getConnection("", "", ""); } catch (SQLException e) { e.printStackTrace(); } return null; } }; public Connection getConnection() { return dbConnectionLocal.get(); } }
這樣在Client獲取Connection的時候, 每個線程獲取到的Connection都是該線程獨有的, 做到Connection的線程隔離; 所以并不存在線程安全問題
ThreadLocal如何實現線程隔離1、主要是用到了Thread對象中的一個ThreadLocalMap類型的變量threadLocals, 負責存儲當前線程的關于Connection的對象, 以dbConnectionLocal 這個變量為Key, 以新建的Connection對象為Value; 這樣的話, 線程第一次讀取的時候如果不存在就會調用ThreadLocal的initialValue方法創建一個Connection對象并且返回;
具體關于為線程分配變量副本的代碼如下:
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(); }
1、首先獲取當前線程對象t, 然后從線程t中獲取到ThreadLocalMap的成員屬性threadLocals
2、如果當前線程的threadLocals已經初始化(即不為null) 并且存在以當前ThreadLocal對象為Key的值, 則直接返回當前線程要獲取的對象(本例中為Connection);
3、如果當前線程的threadLocals已經初始化(即不為null)但是不存在以當前ThreadLocal對象為Key的的對象, 那么重新創建一個Connection對象, 并且添加到當前線程的threadLocals Map中,并返回
4、如果當前線程的threadLocals屬性還沒有被初始化, 則重新創建一個ThreadLocalMap對象, 并且創建一個Connection對象并添加到ThreadLocalMap對象中并返回。
如果存在則直接返回很好理解, 那么對于如何初始化的代碼又是怎樣的呢?
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; }
1、首先調用我們上面寫的重載過后的initialValue方法, 產生一個Connection對象
2、繼續查看當前線程的threadLocals是不是空的, 如果ThreadLocalMap已被初始化, 那么直接將產生的對象添加到ThreadLocalMap中, 如果沒有初始化, 則創建并添加對象到其中;
同時, ThreadLocal還提供了直接操作Thread對象中的threadLocals的方法
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
這樣我們也可以不實現initialValue, 將初始化工作放到DBConnectionFactory的getConnection方法中:
public Connection getConnection() { Connection connection = dbConnectionLocal.get(); if (connection == null) { try { connection = DriverManager.getConnection("", "", ""); dbConnectionLocal.set(connection); } catch (SQLException e) { e.printStackTrace(); } } return connection; }
那么我們看過代碼之后就很清晰的知道了為什么ThreadLocal能夠實現變量的多線程隔離了; 其實就是用了Map的數據結構給當前線程緩存了, 要使用的時候就從本線程的threadLocals對象中獲取就可以了, key就是當前線程;
當然了在當前線程下獲取當前線程里面的Map里面的對象并操作肯定沒有線程并發問題了, 當然能做到變量的線程間隔離了;
現在我們知道了ThreadLocal到底是什么了, 又知道了如何使用ThreadLocal以及其基本實現原理了是不是就可以結束了呢? 其實還有一個問題就是ThreadLocalMap是個什么對象, 為什么要用這個對象呢?
ThreadLocalMap對象是什么本質上來講, 它就是一個Map, 但是這個ThreadLocalMap與我們平時見到的Map有點不一樣
1、它沒有實現Map接口;
2、它沒有public的方法, 最多有一個default的構造方法, 因為這個ThreadLocalMap的方法僅僅在ThreadLocal類中調用, 屬于靜態內部類
3、ThreadLocalMap的Entry實現繼承了WeakReference
4、該方法僅僅用了一個Entry數組來存儲Key, Value; Entry并不是鏈表形式, 而是每個bucket里面僅僅放一個Entry;
要了解ThreadLocalMap的實現, 我們先從入口開始, 就是往該Map中添加一個值:
private void set(ThreadLocal> key, Object value) { // We don"t use a fast path as with get() because it is at // least as common to use set() to create new entries as // it is to replace existing ones, in which case, a fast // path would fail more often than not. Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal> k = e.get(); if (k == key) { e.value = value; return; } if (k == null) { replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
先進行簡單的分析, 對該代碼表層意思進行解讀:
1、看下當前threadLocal的在數組中的索引位置 比如: `i = 2`, 看 `i = 2` 位置上面的元素(Entry)的`Key`是否等于threadLocal 這個 Key, 如果等于就很好說了, 直接將該位置上面的Entry的Value替換成最新的就可以了; 2、如果當前位置上面的 Entry 的 Key為空, 說明ThreadLocal對象已經被回收了, 那么就調用replaceStaleEntry 3、如果清理完無用條目(ThreadLocal被回收的條目)、并且數組中的數據大小 > 閾值的時候對當前的Table進行重新哈希
所以, 該HashMap是處理沖突檢測的機制是向后移位, 清除過期條目 最終找到合適的位置;
了解完Set方法, 后面就是Get方法了:
private Entry getEntry(ThreadLocal> key) { int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i]; if (e != null && e.get() == key) return e; else return getEntryAfterMiss(key, i, e); }
先找到ThreadLocal的索引位置, 如果索引位置處的entry不為空并且鍵與threadLocal是同一個對象, 則直接返回; 否則去后面的索引位置繼續查找;
使用ThreadLocal造成內存泄露import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class ThreadLocalDemo { static class LocalVariable { private Long[] a = new Long[1024 * 1024]; } // (1) final static ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5, 5, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>()); // (2) final static ThreadLocallocalVariable = new ThreadLocal (); public static void main(String[] args) throws InterruptedException { // (3) Thread.sleep(5000 * 4); for (int i = 0; i < 50; ++i) { poolExecutor.execute(new Runnable() { public void run() { // (4) localVariable.set(new LocalVariable()); // (5) System.out.println("use local varaible" + localVariable.get()); localVariable.remove(); } }); } // (6) System.out.println("pool execute over"); } }
我在網上找到一個樣例, 如果用線程池來操作ThreadLocal 對象確實會造成內存泄露, 因為對于線程池里面不會銷毀的線程, 里面總會存在著
所以, 為了避免出現內存泄露的情況, ThreadLocal提供了一個清除線程中對象的方法, 即 remove, 其實內部實現就是調用 ThreadLocalMap 的remove方法:
private void remove(ThreadLocal> key) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { if (e.get() == key) { e.clear(); expungeStaleEntry(i); return; } } }
找到Key對應的Entry, 并且清除Entry的Key(ThreadLocal)置空, 隨后清除過期的Entry即可避免內存泄露;
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/73547.html
摘要:理解內存模型對多線程編程無疑是有好處的。干貨高級動畫高級動畫進階,矢量動畫。 這是最好的Android相關原創知識體系(100+篇) 知識體系從2016年開始構建,所有的文章都是圍繞著這個知識體系來寫,目前共收入了100多篇原創文章,其中有一部分未收入的文章在我的新書《Android進階之光》中。最重要的是,這個知識體系仍舊在成長中。 Android 下拉刷新庫,這一個就夠了! 新鮮出...
以下是Java技術棧微信公眾號發布的關于 Java 的技術干貨,從以下幾個方面匯總。 Java 基礎篇 Java 集合篇 Java 多線程篇 Java JVM篇 Java 進階篇 Java 新特性篇 Java 工具篇 Java 書籍篇 Java基礎篇 8張圖帶你輕松溫習 Java 知識 Java父類強制轉換子類原則 一張圖搞清楚 Java 異常機制 通用唯一標識碼UUID的介紹及使用 字符串...
摘要:方法,刪除當前線程綁定的這個副本數字,這個值是的值,普通的是使用鏈表來處理沖突的,但是是使用線性探測法來處理沖突的,就是每次增加的步長,根據參考資料所說,選擇這個數字是為了讓沖突概率最小。 showImg(https://segmentfault.com/img/remote/1460000019828633); 老套路,先列舉下關于ThreadLocal常見的疑問,希望可以通過這篇學...
閱讀 2125·2019-08-29 16:53
閱讀 2700·2019-08-29 16:07
閱讀 2043·2019-08-29 13:13
閱讀 3268·2019-08-26 13:57
閱讀 1332·2019-08-26 13:31
閱讀 2434·2019-08-26 13:22
閱讀 1222·2019-08-26 11:43
閱讀 2085·2019-08-23 17:14