自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

使用ThreadLocal差點(diǎn)讓我懷疑自己見(jiàn)鬼了

開(kāi)發(fā) 前端
使用ThreadLocal來(lái)存儲(chǔ)數(shù)據(jù)庫(kù)連接對(duì)象Connection,從而每次操作數(shù)據(jù)庫(kù)表都是使用同一個(gè)對(duì)象保障了事務(wù)。

[[443312]]

前言

最近使用ThreadLocal出現(xiàn)了一個(gè)生產(chǎn)問(wèn)題

一大清早就接到業(yè)務(wù)人員的電話(huà),說(shuō)系統(tǒng)登錄進(jìn)去后總是莫名其妙的報(bào)錯(cuò),而且有點(diǎn)隨機(jī)...昏沉的腦袋瞬間清醒了,我問(wèn)具體是哪個(gè)模塊報(bào)錯(cuò),是不是操作了哪些特定的功能才報(bào)錯(cuò),得到的回答是否定的,任何功能操作都隨機(jī)報(bào)錯(cuò)??,也就是有時(shí)候報(bào)錯(cuò),有時(shí)候不報(bào)錯(cuò)。

一時(shí)間有點(diǎn)懵逼了,腦海里不斷回憶這段時(shí)間是不是上了什么新版本,不對(duì)啊,最近也沒(méi)有什么大版本啊,都是一些小改,不可能會(huì)影響到所有業(yè)務(wù)模塊啊。

趕忙起床去公司~

到公司后趕忙去機(jī)房,查看后臺(tái)日志,發(fā)現(xiàn)報(bào)的是空指針異常,接著繼續(xù)定位代碼,發(fā)現(xiàn)是這段代碼是從鏈路日志模塊報(bào)出來(lái)的,仔細(xì)看了下代碼,發(fā)現(xiàn)報(bào)錯(cuò)是從鏈路日志那塊報(bào)出來(lái)的,這塊代碼看起來(lái)也沒(méi)啥問(wèn)題,而且這個(gè)模塊都投產(chǎn)好幾個(gè)月了,從來(lái)都沒(méi)有發(fā)生過(guò)類(lèi)似的報(bào)錯(cuò),跟了下代碼,是從ThreadLocal中取值,第一反應(yīng)是鏈路日志又問(wèn)題,先不管了,業(yè)務(wù)催的緊,先把應(yīng)用重啟了。

說(shuō)來(lái)也奇怪,重啟后應(yīng)用竟然沒(méi)有再出現(xiàn)報(bào)錯(cuò)了,真的絕了,這下我更加好奇了,在開(kāi)發(fā)環(huán)境進(jìn)行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、....相關(guān)業(yè)務(wù)代碼 
  9.  
  10. 5、threadlocal.get() 獲取鏈路日志相關(guān)信息進(jìn)行相關(guān)的處理 
  11.  
  12. 6、threadlocal.remove() 

咋一看,沒(méi)啥問(wèn)題,然而由于異常的信息導(dǎo)致第4步出現(xiàn)了異常,catch住了但是沒(méi)有在finally里操作threadlocal.remove(),又因?yàn)榈?步的判空對(duì)該線(xiàn)程無(wú)效了(這個(gè)線(xiàn)程已經(jīng)被設(shè)置值了),從而該線(xiàn)程被污染了,

也就是每次用到這個(gè)被污染的線(xiàn)程就會(huì)報(bào)錯(cuò),生產(chǎn)的隨機(jī)報(bào)錯(cuò)就是這么來(lái)的,話(huà)不多說(shuō)修bug。至此問(wèn)題也解決了。

吸取教訓(xùn):使用ThreadLocal時(shí)一定要記得考慮清楚場(chǎng)景,把各種情況都考慮全。

下面是對(duì)ThreadLocal的一些操作

沒(méi)有進(jìn)行remove操作

  1. static ThreadLocal<Integer> threadLocal = new ThreadLocal<>(); 
  2.     // 沒(méi)有進(jìn)行remove操作的ThreadLocal的表現(xiàn) 
  3.     public static void main(String[] args) throws InterruptedException { 
  4.         // 創(chuàng)建一個(gè)線(xiàn)程池 
  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("******************線(xiàn)程" + Thread.currentThread().getName().substring(7) + "設(shè)置threadlocal前的值是: " + integer); 
  11.      
  12.                 if (StringUtils.isEmpty(threadLocal.get())) { 
  13.                     threadLocal.set(count); 
  14.                 } 
  15.                 System.out.println("******************線(xiàn)程" + Thread.currentThread().getName().substring(7) + "里面的值是: " + threadLocal.get()); 
  16.             }); 
  17.             Thread.sleep(100); 
  18.         } 
  19.     } 

控制臺(tái)打印效果如下,得到錯(cuò)誤答案

進(jìn)行了remove操作

  1. static ThreadLocal<Integer> threadLocal = new ThreadLocal<>(); 
  2.     // 進(jìn)行remove操作的ThreadLocal的表現(xiàn) 
  3.     public static void main(String[] args) throws InterruptedException { 
  4.         // 創(chuàng)建一個(gè)線(xiàn)程池 
  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("******************線(xiàn)程" + Thread.currentThread().getName().substring(7) + "設(shè)置threadlocal前的值是: " + integer); 
  11.  
  12.                 if (StringUtils.isEmpty(threadLocal.get())) { 
  13.                     threadLocal.set(count); 
  14.                 } 
  15.                 System.out.println("******************線(xiàn)程" + Thread.currentThread().getName().substring(7) + "里面的值是: " + threadLocal.get()); 
  16.                 threadLocal.remove(); 
  17.             }); 
  18.             Thread.sleep(100); 
  19.         } 
  20.     } 

控制臺(tái)打印效果如下,得到正確答案

remove操作報(bào)錯(cuò)了

  1. static ThreadLocal<Integer> threadLocal = new ThreadLocal<>(); 
  2.     // 沒(méi)有進(jìn)行remove操作的ThreadLocal的表現(xiàn) 
  3.     public static void main(String[] args) throws InterruptedException { 
  4.         // 創(chuàng)建一個(gè)線(xiàn)程池 
  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("******************線(xiàn)程" + Thread.currentThread().getName().substring(7) + "設(shè)置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("******************線(xiàn)程" + Thread.currentThread().getName().substring(7) + "里面的值是: " + threadLocal.get()); 
  21.                 threadLocal.remove(); 
  22.             } catch (Exception e) {} 
  23.             }); 
  24.             Thread.sleep(100); 
  25.         } 
  26.     } 

控制臺(tái)打印效果如下,雖然進(jìn)行了catch但是沒(méi)有在finally里進(jìn)行remove操作,得到錯(cuò)誤答案

再修改得到最終代碼

  1. static ThreadLocal<Integer> threadLocal = new ThreadLocal<>(); 
  2.     // 沒(méi)有進(jìn)行remove操作的ThreadLocal的表現(xiàn) 
  3.     public static void main(String[] args) throws InterruptedException { 
  4.         // 創(chuàng)建一個(gè)線(xiàn)程池 
  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("******************線(xiàn)程" + Thread.currentThread().getName().substring(7) + "設(shè)置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("******************線(xiàn)程" + 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用于線(xiàn)程間的數(shù)據(jù)隔離,一說(shuō)到線(xiàn)程間的數(shù)據(jù)隔離,我們還能想到synchronized或者其他的鎖來(lái)實(shí)現(xiàn)線(xiàn)程間的安全問(wèn)題。

ThreadLocal適合什么樣的業(yè)務(wù)場(chǎng)景

1、使用threadlocal存儲(chǔ)數(shù)據(jù)庫(kù)連接,如果說(shuō)一次線(xiàn)程請(qǐng)求,需要同時(shí)更新Goods表和Goods_Detail表,要是直接new出2個(gè)數(shù)據(jù)庫(kù)連接,那么事務(wù)就沒(méi)法進(jìn)行保障了,數(shù)據(jù)庫(kù)連接池

使用ThreadLocal來(lái)存儲(chǔ)數(shù)據(jù)庫(kù)連接對(duì)象Connection,從而每次操作數(shù)據(jù)庫(kù)表都是使用同一個(gè)對(duì)象保障了事務(wù)。

2、解決SimpleDataFormat的線(xiàn)程安全問(wèn)題

3、基于hreadlocal的數(shù)據(jù)源的動(dòng)態(tài)切換

4、使用ThreadLocal來(lái)存儲(chǔ)Cookie對(duì)象,在這次Http請(qǐng)求中,任何時(shí)候都可以通過(guò)簡(jiǎn)單的方式獲取到Cookie。

當(dāng)ThreadLocal被設(shè)置后綁定了當(dāng)前線(xiàn)程,如果線(xiàn)程希望當(dāng)前線(xiàn)程的子線(xiàn)程也能獲取到該值,這就是InheritableThreadLocal的用武之地了

如何傳遞給子線(xiàn)程呢?InheritableThreadLocal的具體使用如下:

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

 

責(zé)任編輯:武曉燕 來(lái)源: 程序員巴士
相關(guān)推薦

2019-06-21 15:23:08

Python面試題代碼

2020-04-17 10:23:43

TDD測(cè)試驅(qū)動(dòng)

2021-10-22 05:56:31

數(shù)據(jù)庫(kù)鎖表鎖定機(jī)制

2012-07-25 09:56:52

編程程序員

2023-11-02 08:27:29

2022-02-21 12:29:01

for循環(huán)前端

2019-07-09 05:29:56

木馬病毒網(wǎng)絡(luò)安全

2020-05-25 09:45:47

開(kāi)發(fā)技能代碼

2020-08-04 08:44:08

HashCode

2023-03-27 07:39:07

內(nèi)存溢出優(yōu)化

2023-05-14 22:25:33

內(nèi)存CPU

2017-09-06 15:40:36

大數(shù)據(jù)動(dòng)向

2009-11-16 17:38:32

博科資訊ERP

2020-08-13 07:56:48

JDK枚舉類(lèi)安全

2011-06-27 08:35:28

2020-05-29 08:14:49

代碼Try-Catch程序員

2018-05-23 11:43:59

數(shù)據(jù)庫(kù)

2013-05-13 11:51:29

2018-06-07 09:32:07

2015-09-09 08:45:49

JavaThreadLocal
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)