你還以為使用 StringBuffer 就萬(wàn)事大吉了?

Java技術(shù)棧
www.javastack.cn
關(guān)注閱讀更多優(yōu)質(zhì)文章
你還以為StringBuffer就萬(wàn)事大吉?別天真了。
每一個(gè)學(xué)過(guò)java的小伙伴都會(huì)背,StringBuffer是線程安全的,StringBuilder是非線程安全的;Hashtable是線程安全的,HashMap是非線程安全的。把這幾條當(dāng)成公理在用了,我面試的同學(xué)中,不管能力好壞,這幾句都能背出來(lái)。
我們看一下StringBuffer的官方注釋?zhuān)?/p>
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.
就連官方的注釋上也寫(xiě)著,StringBuffer是一個(gè)線程安全的可變的字符序列。StringBuffer可以安全的在多線程場(chǎng)景下使用。
事實(shí)真的是這樣的嗎?還真不是。雖然StringBuffer的大部分方法都加了synchronized修飾,但是在真實(shí)使用場(chǎng)景下只能說(shuō)然并卵了,基本上任何出現(xiàn)StringBuffer的地方都可以用StringBuilder去替換。
為什么使用StringBuffer仍不是萬(wàn)事大吉
首先咱們得定義什么是線程安全,線程安全就是在多線程運(yùn)行的環(huán)境下,最終輸出結(jié)果是正確的。其實(shí)任何一個(gè)類(lèi),即便它的所有方法都是synchonized,你也不能無(wú)中生有、暗度陳倉(cāng)、憑空想象、胡作非為。
咱們看一下StringBuffer的常用方法:

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

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

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





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


