您好,我是湘王,這是我的部落格園,歡迎您來,歡迎您再來~
為了提高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操作(但很多碼農不知道這一點)。
感謝您的大駕光臨!諮詢技術、產品、運營和管理相關問題,請關注後留言。歡迎騷擾,不勝榮幸~