您好,我是湘王,這是我的博客園,歡迎您來,歡迎您再來~ 為了提高CPU的利用率,工程師們創造了多線程。但是線程們說:要有光!(為了減少線程創建(T1啟動)和銷毀(T3切換)的時間),於是工程師們又接著創造了線程池ThreadPool。就這樣就可以了嗎?——不,工程師們並不滿足於此,他們不把自己創造出 ...
您好,我是湘王,這是我的博客園,歡迎您來,歡迎您再來~
為了提高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 = 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 = 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操作(但很多碼農不知道這一點)。
感謝您的大駕光臨!咨詢技術、產品、運營和管理相關問題,請關註後留言。歡迎騷擾,不勝榮幸~