成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

使用ThreadLocal差點讓我懷疑自己見鬼了

開發 前端
使用ThreadLocal來存儲數據庫連接對象Connection,從而每次操作數據庫表都是使用同一個對象保障了事務。

[[443312]]

前言

最近使用ThreadLocal出現了一個生產問題

一大清早就接到業務人員的電話,說系統登錄進去后總是莫名其妙的報錯,而且有點隨機...昏沉的腦袋瞬間清醒了,我問具體是哪個模塊報錯,是不是操作了哪些特定的功能才報錯,得到的回答是否定的,任何功能操作都隨機報錯??,也就是有時候報錯,有時候不報錯。

一時間有點懵逼了,腦海里不斷回憶這段時間是不是上了什么新版本,不對啊,最近也沒有什么大版本啊,都是一些小改,不可能會影響到所有業務模塊啊。

趕忙起床去公司~

到公司后趕忙去機房,查看后臺日志,發現報的是空指針異常,接著繼續定位代碼,發現是這段代碼是從鏈路日志模塊報出來的,仔細看了下代碼,發現報錯是從鏈路日志那塊報出來的,這塊代碼看起來也沒啥問題,而且這個模塊都投產好幾個月了,從來都沒有發生過類似的報錯,跟了下代碼,是從ThreadLocal中取值,第一反應是鏈路日志又問題,先不管了,業務催的緊,先把應用重啟了。

說來也奇怪,重啟后應用竟然沒有再出現報錯了,真的絕了,這下我更加好奇了,在開發環境進行debug,那塊代碼邏輯的偽代碼如下

  1. // 偽代碼 
  2. 1、ThreadLocal的初始化 
  3.  
  4. 2、ThreadLocal threadlocal = new ThreadLocal(); 
  5.  
  6. 3、if(threadlocal.get() == null) threadlocal.set(XX) 
  7.  
  8. 4、....相關業務代碼 
  9.  
  10. 5、threadlocal.get() 獲取鏈路日志相關信息進行相關的處理 
  11.  
  12. 6、threadlocal.remove() 

咋一看,沒啥問題,然而由于異常的信息導致第4步出現了異常,catch住了但是沒有在finally里操作threadlocal.remove(),又因為第3步的判空對該線程無效了(這個線程已經被設置值了),從而該線程被污染了,

也就是每次用到這個被污染的線程就會報錯,生產的隨機報錯就是這么來的,話不多說修bug。至此問題也解決了。

吸取教訓:使用ThreadLocal時一定要記得考慮清楚場景,把各種情況都考慮全。

下面是對ThreadLocal的一些操作

沒有進行remove操作

  1. static ThreadLocal<Integer> threadLocal = new ThreadLocal<>(); 
  2.     // 沒有進行remove操作的ThreadLocal的表現 
  3.     public static void main(String[] args) throws InterruptedException { 
  4.         // 創建一個線程池 
  5.         ExecutorService pool = Executors.newFixedThreadPool(2); 
  6.         for (int i = 0; i <= 5; i++) { 
  7.             final int count = i; 
  8.             pool.execute(()->{ 
  9.                 Integer integer = threadLocal.get(); 
  10.                 System.out.println("******************線程" + Thread.currentThread().getName().substring(7) + "設置threadlocal前的值是: " + integer); 
  11.      
  12.                 if (StringUtils.isEmpty(threadLocal.get())) { 
  13.                     threadLocal.set(count); 
  14.                 } 
  15.                 System.out.println("******************線程" + Thread.currentThread().getName().substring(7) + "里面的值是: " + threadLocal.get()); 
  16.             }); 
  17.             Thread.sleep(100); 
  18.         } 
  19.     } 

控制臺打印效果如下,得到錯誤答案

進行了remove操作

  1. static ThreadLocal<Integer> threadLocal = new ThreadLocal<>(); 
  2.     // 進行remove操作的ThreadLocal的表現 
  3.     public static void main(String[] args) throws InterruptedException { 
  4.         // 創建一個線程池 
  5.         ExecutorService pool = Executors.newFixedThreadPool(2); 
  6.         for (int i = 0; i <= 5; i++) { 
  7.             final int count = i; 
  8.             pool.execute(()->{ 
  9.                 Integer integer = threadLocal.get(); 
  10.                 System.out.println("******************線程" + Thread.currentThread().getName().substring(7) + "設置threadlocal前的值是: " + integer); 
  11.  
  12.                 if (StringUtils.isEmpty(threadLocal.get())) { 
  13.                     threadLocal.set(count); 
  14.                 } 
  15.                 System.out.println("******************線程" + Thread.currentThread().getName().substring(7) + "里面的值是: " + threadLocal.get()); 
  16.                 threadLocal.remove(); 
  17.             }); 
  18.             Thread.sleep(100); 
  19.         } 
  20.     } 

控制臺打印效果如下,得到正確答案

remove操作報錯了

  1. static ThreadLocal<Integer> threadLocal = new ThreadLocal<>(); 
  2.     // 沒有進行remove操作的ThreadLocal的表現 
  3.     public static void main(String[] args) throws InterruptedException { 
  4.         // 創建一個線程池 
  5.         ExecutorService pool = Executors.newFixedThreadPool(2); 
  6.         for (int i = 0; i <= 5; i++) { 
  7.  
  8.             final int count = i; 
  9.             pool.execute(()-> { 
  10.                 try { 
  11.                 Integer integer = threadLocal.get(); 
  12.                 System.out.println("******************線程" + Thread.currentThread().getName().substring(7) + "設置threadlocal前的值是: " + integer); 
  13.  
  14.                 if (StringUtils.isEmpty(threadLocal.get())) { 
  15.                     threadLocal.set(count); 
  16.                 } 
  17.                 if (Thread.currentThread().getName().contains("thread-1")) { 
  18.                     throw new RuntimeException(); 
  19.                 } 
  20.                 System.out.println("******************線程" + Thread.currentThread().getName().substring(7) + "里面的值是: " + threadLocal.get()); 
  21.                 threadLocal.remove(); 
  22.             } catch (Exception e) {} 
  23.             }); 
  24.             Thread.sleep(100); 
  25.         } 
  26.     } 

控制臺打印效果如下,雖然進行了catch但是沒有在finally里進行remove操作,得到錯誤答案

再修改得到最終代碼

  1. static ThreadLocal<Integer> threadLocal = new ThreadLocal<>(); 
  2.     // 沒有進行remove操作的ThreadLocal的表現 
  3.     public static void main(String[] args) throws InterruptedException { 
  4.         // 創建一個線程池 
  5.         ExecutorService pool = Executors.newFixedThreadPool(2); 
  6.         for (int i = 0; i <= 5; i++) { 
  7.  
  8.             final int count = i; 
  9.             pool.execute(()-> { 
  10.                 try { 
  11.                 Integer integer = threadLocal.get(); 
  12.                 System.out.println("******************線程" + Thread.currentThread().getName().substring(7) + "設置threadlocal前的值是: " + integer); 
  13.  
  14.                 if (StringUtils.isEmpty(threadLocal.get())) { 
  15.                     threadLocal.set(count); 
  16.                 } 
  17.                 if (Thread.currentThread().getName().contains("thread-1")) { 
  18.                     throw new RuntimeException(); 
  19.                 } 
  20.                 System.out.println("******************線程" + Thread.currentThread().getName().substring(7) + "里面的值是: " + threadLocal.get()); 
  21.                  
  22.             } catch (Exception e) {.....}  
  23.              finally { 
  24.                threadLocal.remove();  
  25.              } 
  26.             }); 
  27.             Thread.sleep(100); 
  28.         } 
  29.     } 

ThreadLocal用于線程間的數據隔離,一說到線程間的數據隔離,我們還能想到synchronized或者其他的鎖來實現線程間的安全問題。

ThreadLocal適合什么樣的業務場景

1、使用threadlocal存儲數據庫連接,如果說一次線程請求,需要同時更新Goods表和Goods_Detail表,要是直接new出2個數據庫連接,那么事務就沒法進行保障了,數據庫連接池

使用ThreadLocal來存儲數據庫連接對象Connection,從而每次操作數據庫表都是使用同一個對象保障了事務。

2、解決SimpleDataFormat的線程安全問題

3、基于hreadlocal的數據源的動態切換

4、使用ThreadLocal來存儲Cookie對象,在這次Http請求中,任何時候都可以通過簡單的方式獲取到Cookie。

當ThreadLocal被設置后綁定了當前線程,如果線程希望當前線程的子線程也能獲取到該值,這就是InheritableThreadLocal的用武之地了

如何傳遞給子線程呢?InheritableThreadLocal的具體使用如下:

  1. // 創建InheritableThreadLocal 
  2.   static ThreadLocal<Integer> threadLocaltest = new InheritableThreadLocal<>(); 
  3.   public static void main(String[] args) { 
  4. // 主線程設置值 
  5.       threadLocaltest.set(100); 
  6.       new Thread(()-> { 
  7.     // 子線程獲取值 
  8.           Integer num = threadLocaltest.get(); 
  9.  // 子線程獲取到值并打印出來 
  10.           System.out.println(Thread.currentThread().getName() + "子類獲取到的值" + num); // 輸出:Thread-0子類獲取到的值100 
  11.       }).start(); 
  12.   } 

 

責任編輯:武曉燕 來源: 程序員巴士
相關推薦

2019-06-21 15:23:08

Python面試題代碼

2020-04-17 10:23:43

TDD測試驅動

2021-10-22 05:56:31

數據庫鎖表鎖定機制

2012-07-25 09:56:52

編程程序員

2022-02-21 12:29:01

for循環前端

2023-11-02 08:27:29

2009-11-16 17:38:32

博科資訊ERP

2020-08-04 08:44:08

HashCode

2019-07-09 05:29:56

木馬病毒網絡安全

2017-09-06 15:40:36

大數據動向

2020-05-25 09:45:47

開發技能代碼

2020-08-13 07:56:48

JDK枚舉類安全

2023-03-27 07:39:07

內存溢出優化

2023-05-14 22:25:33

內存CPU

2011-06-27 08:35:28

2020-05-29 08:14:49

代碼Try-Catch程序員

2013-05-13 11:51:29

2018-06-07 09:32:07

2018-05-23 11:43:59

數據庫

2019-03-06 15:04:35

Google安全WebView
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 日韩av在线免费 | 一区二区视频在线观看 | 一区二区免费在线观看 | 免费爱爱视频 | 欧美日韩理论 | 精品一区二区在线观看 | 久久国产一区二区三区 | 国产精品久久影院 | 成人av一区二区三区 | 亚洲日韩中文字幕一区 | 久久久99精品免费观看 | 蜜臀网| 国产精品一区二区视频 | 99在线免费观看视频 | 99精品国产一区二区青青牛奶 | 国产精品18hdxxxⅹ在线 | 亚洲精品国产a久久久久久 午夜影院网站 | 欧美日韩在线精品 | 日韩一区二区三区av | 国产免费a视频 | 亚洲精品久久久久中文字幕欢迎你 | 天天草av | 亚洲国产精品一区 | 久久久久免费 | 久久精品欧美视频 | 国产日韩欧美精品一区二区三区 | 中文字幕视频在线 | 福利视频二区 | 欧美一级在线观看 | 亚洲一区二区在线视频 | 国产精品观看 | 亚洲精品在线免费观看视频 | 欧美一级二级视频 | 久久久九九 | 国产最新网址 | 91久久精品国产91久久性色tv | 一区二区三区在线电影 | 国产欧美久久一区二区三区 | 中文字幕av网站 | 精品久久香蕉国产线看观看亚洲 | 久久国产精品偷 |