您好,我是湘王,這是我的博客園,歡迎您來,歡迎您再來~
為了提高CPU的利用率,工程師們創造了多執行緒,但是執行緒們說:要有光!(為了減少執行緒創建(T1啟動)和銷毀(T3切換)的時間),于是工程師們又接著創造了執行緒池ThreadPool,就這樣就可以了嗎?——不,工程師們并不滿足于此,他們不把自己創造出來的執行緒給扒個底朝天決不罷手,
有了執行緒關鍵字解決執行緒安全問題,有了執行緒池解決效率問題,那還有什么問題是可以需要被解決的呢?——還真被這幫瘋子攻城獅給找到了!
當多個執行緒共享同一個資源的時候,為了保證執行緒安全,有時不得不給資源加鎖,例如使用Synchronized關鍵字實作同步鎖,這本質上其實是一種時間換空間的搞法——用單一資源讓不同的執行緒依次訪問,從而實作內容安全可控,就像這樣:

但是,可以不可以反過來,將資源拷貝成多份副本的形式來同時訪問,達到一種空間換時間的效果呢?當然可以,就像這樣:

而這,就是ThreadLocal最核心的思想,
但這種方式在很多應用級開發的場景中用得真心不多,而且有些公司還禁止使用ThreadLocal,因為它搞不好還會帶來一些負面影響,
其實,從拷貝若干副本這種功能來看,ThreadLocal是實作了在執行緒內部存盤資料的能力的,而且相互之間還能通信,就像這樣:

還是以代碼的形式來解讀一下ThreadLocal,有一個資源類Resource:
/** * 資源類 * * @author 湘王 */ public class Resource { private String name; private String value; public Resource(String name, String value) { super(); this.name = name; this.value =https://www.cnblogs.com/xiangwang1111/archive/2022/10/27/ value; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getValue() { return value; } public void setValue(String value) { this.value =https://www.cnblogs.com/xiangwang1111/archive/2022/10/27/ value; } }
分別有ResuorceUtils1、ResuorceUtils2和ResuorceUtils3分別以不同的方式來連接資源,那么看看效率如何,
/** * 連接資源工具類,通過靜態方式獲得連接 * * @author 湘王 */ public class ResourceUtils1 { // 定義一個靜態連接資源 private static Resource resource = null; // 獲取連接資源 public static Resource getResource() { if(resource == null) { resource = new Resource("xiangwang", "123456"); } return resource; } // 關閉連接資源 public static void closeResource() { if(resource != null) { resource = null; } } } /** * 連接資源工具類,通過實體化方式獲得連接 * * @author 湘王 */ public class ResourceUtils2 { // 定義一個連接資源 private Resource resource = null; // 獲取連接資源 public Resource getResource() { if(resource == null) { resource = new Resource("xiangwang", "123456"); } return resource; } // 關閉連接資源 public void closeResource() { if(resource != null) { resource = null; } } } /** * 連接資源工具類,通過執行緒中的static Connection的副本方式獲得連接 * * @author 湘王 */ public class ResourceUtils3 { // 定義一個靜態連接資源 private static Resource resource = null; private static ThreadLocal<Resource> resourceContainer = new ThreadLocal<Resource>(); // 獲取連接資源 public static Resource getResource() { synchronized(ResourceManager.class) { resource = resourceContainer.get(); if(resource == null) { resource = new Resource("xiangwang", "123456"); resourceContainer.set(resource); } return resource; } } // 關閉連接資源 public static void closeResource() { if(resource != null) { resource = null; resourceContainer.remove(); } } } /** * 連接資源管理類 * * @author 湘王 */ public class ResourceManager { public void insert() { // 獲取連接 // System.out.println("Dao.insert()-->" + Thread.currentThread().getName() + ResourceUtils1.getResource()); // Resource resource = new ResourceUtils2().getResource(); Resource resource = ResourceUtils3.getResource(); System.out.println("Dao.insert()-->" + Thread.currentThread().getName() + resource); } public void delete() { // 獲取連接 // System.out.println("Dao.delete()-->" + Thread.currentThread().getName() + ResourceUtils1.getResource()); // Resource resource = new ResourceUtils2().getResource(); Resource resource = ResourceUtils3.getResource(); System.out.println("Dao.delete()-->" + Thread.currentThread().getName() + resource); } public void update() { // 獲取連接 // System.out.println("Dao.update()-->" + Thread.currentThread().getName() + ResourceUtils1.getResource()); // Resource resource = new ResourceUtils2().getResource(); Resource resource = ResourceUtils3.getResource(); System.out.println("Dao.update()-->" + Thread.currentThread().getName() + resource); } public void select() { // 獲取連接 // System.out.println("Dao.select()-->" + Thread.currentThread().getName() + ResourceUtils1.getResource()); // Resource resource = new ResourceUtils2().getResource(); Resource resource = ResourceUtils3.getResource(); System.out.println("Dao.select()-->" + Thread.currentThread().getName() + resource); } public void close() { ResourceUtils3.closeResource(); } public static void main(String[] args) { for (int i = 0; i < 3; i++) { new Thread(new Runnable() { ResourceManager rm = new ResourceManager(); @Override public void run() { rm.insert(); rm.delete(); rm.update(); rm.select(); rm.close(); } }).start(); } } }
執行ResourceManager類中的main()方法后,可以清楚地看到:
第一種靜態方式:大部分資源都能復用,但毫無規律;
第二種實體方式:即使是同一個執行緒,資源實體也不一樣;
第三種ThreadLocal靜態方式:相同的執行緒有相同的實體,
結論是:ThreadLocal實作了執行緒的資源復用,
也可以通過畫圖的方式來看清楚三者之間的不同:
這是靜態方式下的資源管理:

這是實體方式下的資源管理:

這是ThreadLocal靜態方式下的資源管理:

理解了之后,再來看一個資料傳遞的例子,也就是ThreadLocal實作執行緒間通信的例子:
/** * 資料傳遞 * * @author 湘王 */ public class DataDeliver { static class Data1 { public void process() { Resource resource = new Resource("xiangwang", "123456"); //將物件存盤到ThreadLocal ResourceContextHolder.holder.set(resource); new Data2().process(); } } static class Data2 { public void process() { Resource resource = ResourceContextHolder.holder.get(); System.out.println("Data2拿到資料: " + resource.getName()); new Data3().process(); } } static class Data3 { public void process() { Resource resource = ResourceContextHolder.holder.get(); System.out.println("Data3拿到資料: " + resource.getName()); } } static class ResourceContextHolder { public static ThreadLocal<Resource> holder = new ThreadLocal<>(); } public static void main(String[] args) { new Data1().process(); } }
運行代碼之后,可以看到Data1的資料都被Data2和Data3拿到了,就像這樣:

ThreadLocal在實際應用級開發中較少使用,因為容易造成OOM:
1、由于ThreadLocal是一個弱參考(WeakReference<ThreadLocal<?>>),因此會很容易被GC回收;
2、但ThreadLocalMap的生命周期和Thread相同,這就會造成當key=null時,value卻還存在,造成記憶體泄漏,所以,使用完ThreadLocal后需要顯式呼叫remove操作(但很多碼農不知道這一點),
感謝您的大駕光臨!咨詢技術、產品、運營和管理相關問題,請關注后留言,歡迎騷擾,不勝榮幸~
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/520755.html
標籤:其他
