不規(guī)范使用ThreadLocal導(dǎo)致的bug,說多了都是淚
ThreadLocal一般用于線程間的數(shù)據(jù)隔離,通過將數(shù)據(jù)緩存在ThreadLocal中,可以極大的提升性能。但是,如果錯(cuò)誤的使用Threadlocal,可能會(huì)引起不可預(yù)期的bug,以及造成內(nèi)存泄露。
因?yàn)榫€程重用導(dǎo)致的信息錯(cuò)亂的bug
有時(shí)我們會(huì)在一個(gè)接口中緩存某些數(shù)據(jù)到ThreadLocal中,但是我們要意識(shí)到,處理請(qǐng)求的這些線程是由tomcat提供的,而tomcat提供的線程都是配置在一個(gè)線程池中的。
也就是說,線程是可能被重用的,如果線程一旦被重用,而ThreadLocal的數(shù)據(jù)沒有及時(shí)重置,就會(huì)導(dǎo)致數(shù)據(jù)被混亂使用。
以下方的接口為例,先獲取當(dāng)前線程中保存的數(shù)據(jù)信息,將參數(shù)中的name保存到ThreadLocal中以后,再獲取一次。
為了盡快復(fù)現(xiàn)線程重用導(dǎo)致的問題,我們將
servlet.tomcat.threads.max設(shè)置為1,這樣每次請(qǐng)求使用的都是同一個(gè)線程。
第一次請(qǐng)求接口,數(shù)據(jù)看起來很正常:
但是第二次請(qǐng)求接口時(shí),可以看到線程仍然是http-nio-8080-exec-1,但是before卻打印出了第一次請(qǐng)求的參數(shù)test。
這就是因?yàn)闆]有及時(shí)重置ThreadLocal導(dǎo)致的數(shù)據(jù)錯(cuò)誤。
正確使用的姿勢(shì)
修正的辦法就是處理完接口之后要及時(shí)清理ThreadLocal。
更優(yōu)雅的處理方式
可能也有的朋友會(huì)說,每次都要使用try finally處理線程數(shù)據(jù),未免也太麻煩了。其實(shí),我們可以使用攔截器或者過濾器自動(dòng)幫我們完成數(shù)據(jù)的初始化以及清理工作。
最后
我們?cè)趯憳I(yè)務(wù)代碼時(shí),正確的理解線程的全生命周期以及執(zhí)行原理,對(duì)我們提升代碼的健壯性其實(shí)很有幫助。有時(shí)我們覺得底層原理很枯燥乏味,開發(fā)業(yè)務(wù)就是寫增刪改查,多線程用的也很少,但我們只是沒有意識(shí)到,我們的代碼一直跑在tomcat提供的線程池中,本身就是一個(gè)多線程的環(huán)境。
除了tomcat的線程池,我們自定義的線程池其實(shí)也會(huì)有這個(gè)問題,大家可以看看自己的業(yè)務(wù)代碼是否踩過這個(gè)坑。