線程安全評估是壹個老生常談的問題。
舉例:壹個人的賬戶有兩個終端:銀行卡和存折。* * * 5000元。在銀行卡提現2000元的瞬間,服務器還沒有更新余額(此時應該是3000元),存折也向服務器發送提現3000元的請求,服務器核對剩余5000元後給出提現操作。此時,在存折發行貨幣之前,先前銀行卡的余額被更新到服務器。此時服務器余額為3000元。
好吧,我取了5000元,***5000元,最後我的卡裏還剩2000。然後妳進去了。
2.線程安全的解決方案
(1)在上面的例子中,最常見的解決方案是使用同步語句塊?同步保護撤退過程。當壹個線程正在訪問帳戶並執行取款操作時,其他線程只能等待取款。這種方式就是用時間換空間。
(2)使用ThreadLocal維護變量時,Thread Local為每個使用變量的線程提供獨立的變量副本,因此每個線程都可以獨立更改自己的副本,而不會影響其他線程對應的副本。這種方式就是用空間換時間。
好了,思維的拓展結束了。現在我們來看看這個問題,為什麽字符串是線程安全的?
搜索String的源代碼,可能找不到幾個同步的關鍵字。是的,我在引導妳(2)。
但是我也沒有看到任何與ThreadLocal相關的內容。這是因為在設計String時,字符存儲在壹個char數組中,而這個char數組是最終的。也就是說,無論妳有什麽多線程環境,妳都要修改它。
它對原始對象沒有影響。因為字符串在被更改時指向另壹個對象(即另壹個char數組)。每個線程在被修改時都是壹個獨立的char數組,這正是方法(2)。所以最後壹個字符數組是字符串安全性的基礎。
還有StringBuffer和stringBuilder,它們都放棄使用最終的char數組,因此在拼接字符串時可以節省內存(不需要拼寫壹個字符new和壹個char數組)。但是這使得線程不安全。這是StringBuilder。為什麽StringBuilder是線程安全的?這是因為他的線程安全使用了(1)方法(大量同步)。
這裏我們需要解釋壹下,(2)為什麽該方案可以解決線程安全問題。也可以理解為為什麽使用final char數組可以實現線程安全。原理其實很簡單。我們反過來想,線程的不安全場景無非是當同壹對象同時被修改時,其中壹個線程只運行了壹半。另壹個線程也開始運行。因為線程的執行時間是cpu分配的時間片,所以不是先執行的那個先結束。完全有可能線程A將首先執行修改,而線程B將在修改未完成時開始操作該對象。因此,線程B將首先完成修改。想象壹下,最後的結果肯定不是預期的結果。只有在這種情況下,線程才會不安全。當每個線程都有壹個實例時,這種情況將不再存在。