今天配合同事一起和外部系統(tǒng)進行聯(lián)調(diào)測試,其實昨天我們已經(jīng)成功走通了一遍。今天同事得到對方反饋系統(tǒng)可能有一個潛在的問題,所以就又嚴(yán)格地聯(lián)調(diào)了一遍负间。這一遍偶妖,我也是一遍又一遍地盯日志,關(guān)注告警郵件政溃。正是在這一遍聯(lián)調(diào)系統(tǒng)中趾访,我發(fā)現(xiàn)了一個小問題,程序里面一封相同內(nèi)容的通知郵件董虱,幾乎是同一個時間發(fā)送了兩次扼鞋。
通過分析日志外加比對代碼,終于找到問題愤诱,是遇到線程安全問題引起的云头。下面和大家分享一下,偽代碼:
public class Email{
private String emailContent;
@Async
public void sendEmail(){
"send email" + emailContent;
}
}
//Email類的實例被Spring容器管理转锈,只有一個實例emailInstance盘寡。
public class TestDemo{
@Resource
Email emailInstance;
@Async
public void testMethod(){
lock();
//處理業(yè)務(wù)邏輯楚殿,同時操作emailInstance.emailContent;
operationBs();
emailInstance. sendEmail();
unlock();
}
}
問題原因復(fù)述:testMethod方法為防止多線程同時操作撮慨,在此處使用了鎖,而為了確保發(fā)送郵件不影響主程序的執(zhí)行時間脆粥,所以在調(diào)用sendEmail方法時砌溺,另行開辟了異步線程處理。正是這個方法允許了異步線程處理变隔,所以當(dāng)?shù)谝粋€線程釋放鎖后规伐,而異步線程尚未完成郵件發(fā)送時,第二個獲得鎖并執(zhí)行sendEmail方法時匣缘,開啟了新的異步線程處理郵件發(fā)送猖闪,這樣就出現(xiàn)了多個線程共同使用emailInstance對象,并同時操作emailInstance的成員變量emailContent肌厨。
因為如果一個變量是成員變量培慌,那么多個線程對同一個對象的成員變量進行操作時,他們對該成員變量的操作是彼此影響的(也就是說一個線程對成員變量的改變會影響到另一個線程)柑爸。這樣就會出現(xiàn)上述發(fā)送相同內(nèi)容郵件的情況吵护。
理論講解:JAVA 多線程同時調(diào)用單例模式的對象時,該對象中的對成員變量與局部變量是否會受到多個線程的影響表鳍?
當(dāng)多個線程對同一個單例對象的同一個成員變量進行操作時馅而,它們對該成員變量的操作是彼此互相影響的(也就是說一個線程對該成員變量的改變會影響到另一個線程) 。對于成員變量的操作譬圣,可以使用ThreadLocal來保證線程安全瓮恭。
而多線程調(diào)用同一個對象的同一個方法時,每個線程會對方法內(nèi)部的局部變量都是在線程自己獨立的內(nèi)存區(qū)域進行的厘熟,也就是說在每個線程的獨立內(nèi)存中都一個局部變量的拷貝屯蹦,這樣一個線程對同一個單例對象的同一方法內(nèi)的局部變量的改變就不會影響到其他線程中的局部變量诸衔,所以是線程安全的。
總結(jié)颇玷,局部變量不會受多線程影響笨农,成員變量會受到多線程影響。多個線程調(diào)用同一個對象的同一個方法時帖渠,如果方法里無成員變量谒亦,那么不受任何影響;如果方法里有成員變量空郊,只有讀操作份招,不受影響,存在寫操作狞甚,考慮多線程影響值锁摔。
解決方案:
- 簡化方案,去掉Email中sendEmail方法上@Async注解哼审,也就是說將處理業(yè)務(wù)邏輯和發(fā)送郵件合并為同步操作谐腰,這樣就保證了同一時間只會有一個線程操作成員變量,這樣也就避免了線程安全問題涩盾。但注意這是以犧牲核心業(yè)務(wù)邏輯的處理時間來換得安全十气。
- 將操作成員變量,調(diào)整為操作局部變量春霍。