你還以為使用 StringBuffer 就萬事大吉了?

Java技術(shù)棧
www.javastack.cn
關(guān)注閱讀更多優(yōu)質(zhì)文章
你還以為StringBuffer就萬事大吉?別天真了。
每一個學(xué)過java的小伙伴都會背,StringBuffer是線程安全的,StringBuilder是非線程安全的;Hashtable是線程安全的,HashMap是非線程安全的。把這幾條當(dāng)成公理在用了,我面試的同學(xué)中,不管能力好壞,這幾句都能背出來。
我們看一下StringBuffer的官方注釋:
StringBuffer is A thread-safe, mutable sequence of characters. A string buffer is like a String, but can be modified. At any point in time it contains some particular sequence of characters, but the length and content of the sequence can be changed through certain method calls. String buffers are safe for use by multiple threads. The methods are synchronized where necessary so that all the operations on any particular instance behave as if they occur in some serial order that is consistent with the order of the method calls made by each of the individual threads involved.
就連官方的注釋上也寫著,StringBuffer是一個線程安全的可變的字符序列。StringBuffer可以安全的在多線程場景下使用。
事實真的是這樣的嗎?還真不是。雖然StringBuffer的大部分方法都加了synchronized修飾,但是在真實使用場景下只能說然并卵了,基本上任何出現(xiàn)StringBuffer的地方都可以用StringBuilder去替換。
為什么使用StringBuffer仍不是萬事大吉
首先咱們得定義什么是線程安全,線程安全就是在多線程運(yùn)行的環(huán)境下,最終輸出結(jié)果是正確的。其實任何一個類,即便它的所有方法都是synchonized,你也不能無中生有、暗度陳倉、憑空想象、胡作非為。
咱們看一下StringBuffer的常用方法:

通常我們用的比較多的是append、insert、substring這些方法。你好好想一下,這些方法如果在多線程環(huán)境運(yùn)行的情況下,它能保證程序運(yùn)行結(jié)果的正確性和一致性嗎?
append為例
從參加工作到現(xiàn)在,我遇到的所有append,拼接sql是多較多的,或者是把數(shù)據(jù)庫中的幾個字段拼接成一段話。
如果是多線程環(huán)境運(yùn)行,你根本無法預(yù)測最終結(jié)果是什么,不光是你預(yù)測不了,JVM自己都不知道最終出來的是個什么貨,只能交給天意了
如果有兩個線程同時執(zhí)行append方法
線程1 ?stringBuffer.append(1).append(2)
線程2 ?stringBuffer.append(3).append(4)
你知道最后結(jié)果可能有多少種情況嗎
以insert為例
如果你要insert, 你需要知道自己是要insert到哪一個位置,比如在第一個出現(xiàn)的媳婦前插入一句我愛你三個字,那你寫代碼的話就是兩行代碼
int?index?=?stringBuffer.indexOf(“媳婦”);
if(index?>=?0){
???stringBuffer.insert(index,“我愛你”);
}
同志們,發(fā)現(xiàn)啥問題沒,你要完成這個功能需要三步操作,當(dāng)你完成第一步操作算出index是多少的時候,這時候很可能出現(xiàn)一個不懷好意的第三者線程從中作梗,最后你發(fā)現(xiàn)輸出的結(jié)果根本不是那么回事。
如果你想要這個功能好使,你還是得自己弄把鎖,把剛才的方法鎖住,確保你的操作是原子性的,其他要操作這個stringBuffer的地方,得拿到這把鎖才行。
說了這么多,你發(fā)現(xiàn)了沒,你找不到一個用StringBuffer的理由,我工作這么久是沒見過,不光我沒見過,Effective java的作者josh bloch也說沒見過,他在書中說:
StringBuffer instances are almost always used by a single thread, yet they perform internal synchronization. It is for this reason that StringBuffer was supplanted by StringBuilder, which is just an unsynchronized StringBuffer.
既然無用,那就讓我們來消滅StringBuffer吧
java 5.0在2006年發(fā)布時,提供了StringBuilder這個類,到現(xiàn)在14年已經(jīng)過去了。這個StringBuffer還有屹立不倒的出現(xiàn)在各種代碼中。就連java的源碼中,也到處充斥著無用的StringBuffer。
終于在2014年的某一天,一名叫Paul Sandoz的人實在受不了了,于是給openjdk提了個issue,說咱能不能把java內(nèi)部核心庫中用到StringBuffer的地方替換為StringBuilder

終于在jdk9,把內(nèi)部代碼中用到的StringBuffer給干掉了。我們來做個實驗驗證一下。
寫個簡單的代碼
public?class?Main??{??
???public?static?void?main(final?String[]?args)?throws?IOException??{??
??????System.out.println("Waiting?[press?ENTER?to?exit]?..");??
??????System.in.read();??
???}??
}
然后通過jdk自帶的jcmd工具,分別針對Java 8 Update 102、Java 8 Update 121、OpenJDK 9.0 ea+164三個版本進(jìn)行測試,結(jié)果如下:

可以看到,前面兩個java8的版本,分別有30個StringBuffer實例,而最后的java9的版本,在運(yùn)行前面的示例程序時,是沒有創(chuàng)建任何StringBuffer實例的。
也希望大家能把自己的項目中的StringBuffer清理一下,希望StringBuffer能在下一個10年徹底消失掉。
后記
今年接手的一個項目,在執(zhí)行sonar檢查時,問題最多的就是不應(yīng)該使用StringBuffer,而應(yīng)該使用STringBuilder,足足有將近2000個。
這可怎么改,一天改200個,還得改10天。當(dāng)我分析出一個結(jié)論,就是就沒有在多線程情況下使用StringBuffer的場景,那我就一不做二不休,直接全局替換(但是要悄悄的,不能讓測試同學(xué)知道了),10分鐘替換完,20分鐘編譯通過,搞定!





關(guān)注Java技術(shù)棧看更多干貨


