你还以为StringBuffer就万事大吉?别天真了。

每一个学过java的小伙伴都会背,StringBuffer是线程安全的,StringBuilder是非线程安全的;Hashtable是线程安全的,HashMap是非线程安全的。把这几条当成公理在用了,我面试的同学中,不管能力好坏,这几句都能背出来。

我们看一下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修饰,但是在真实使用场景下只能说然并卵了,基本上任何出现StringBuffer的地方都可以用StringBuilder去替换。

为什么使用StringBuffer仍不是万事大吉

首先咱们得定义什么是线程安全,线程安全就是在多线程运行的环境下,最终输出结果是正确的。其实任何一个类,即便它的所有方法都是synchonized,你也不能无中生有、暗度陈仓、凭空想象、胡作非为。

咱们看一下StringBuffer的常用方法:

java 获取String 大小 字节 java 获取string长度_多线程

通常我们用的比较多的是append、insert、substring这些方法。你好好想一下,这些方法如果在多线程环境运行的情况下,它能保证程序运行结果的正确性和一致性吗?

append为例

从参加工作到现在,我遇到的所有append,拼接sql是多较多的,或者是把数据库中的几个字段拼接成一段话。

如果是多线程环境运行,你根本无法预测最终结果是什么,不光是你预测不了,JVM自己都不知道最终出来的是个什么货,只能交给天意了

如果有两个线程同时执行append方法

线程1  stringBuffer.append(1).append(2)

线程2  stringBuffer.append(3).append(4)

你知道最后结果可能有多少种情况吗

以insert为例

如果你要insert, 你需要知道自己是要insert到哪一个位置,比如在第一个出现的媳妇前插入一句我爱你三个字,那你写代码的话就是两行代码

int index = stringBuffer.indexOf(“媳妇”);
if(index >= 0){
stringBuffer.insert(index,“我爱你”);
}

同志们,发现啥问题没,你要完成这个功能需要三步操作,当你完成第一步操作算出index是多少的时候,这时候很可能出现一个不怀好意的第三者线程从中作梗,最后你发现输出的结果根本不是那么回事。

如果你想要这个功能好使,你还是得自己弄把锁,把刚才的方法锁住,确保你的操作是原子性的,其他要操作这个stringBuffer的地方,得拿到这把锁才行。

说了这么多,你发现了没,你找不到一个用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年发布时,提供了StringBuilder这个类,到现在14年已经过去了。这个StringBuffer还有屹立不倒的出现在各种代码中。就连java的源码中,也到处充斥着无用的StringBuffer。

终于在2014年的某一天,一名叫Paul Sandoz的人实在受不了了,于是给openjdk提了个issue,说咱能不能把java内部核心库中用到StringBuffer的地方替换为StringBuilder


java 获取String 大小 字节 java 获取string长度_java_02

w9w6PI.png

终于在jdk9,把内部代码中用到的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三个版本进行测试,结果如下:

java 获取String 大小 字节 java 获取string长度_线程安全_03

可以看到,前面两个java8的版本,分别有30个StringBuffer实例,而最后的java9的版本,在运行前面的示例程序时,是没有创建任何StringBuffer实例的。

也希望大家能把自己的项目中的StringBuffer清理一下,希望StringBuffer能在下一个10年彻底消失掉。

后记

今年接手的一个项目,在执行sonar检查时,问题最多的就是不应该使用StringBuffer,而应该使用STringBuilder,足足有将近2000个。

这可怎么改,一天改200个,还得改10天。当我分析出一个结论,就是就没有在多线程情况下使用StringBuffer的场景,那我就一不做二不休,直接全局替换(但是要悄悄的,不能让测试同学知道了),10分钟替换完,20分钟编译通过,搞定!