一、概述
在java开发时,我们经常会用到StringBuffer和StringBuilder,且都知道一个结论:StringBuilder不是线程安全的,StringBuffer是线程安全的,至于为什么?可能大多数人一知半解。
下面,我通过代码举例、StringBuffer和StringBuilder源码分析进行解释。
二、代码举例
1、验证StringBuffer是线程安全的
/**
* 验证StringBuffer线程安全,如下,如果length==1000,则可证明
* @throws InterruptedException
*/
public static void testStringBuffer() throws InterruptedException {
StringBuffer sb = new StringBuffer();
for (int i=0; i<10; i++){
new Thread(new Runnable() {
@Override
public void run() {
for (int j=0; j<1000; j++){
sb.append("a");
}
}
}).start();
}
Thread.sleep(100);
System.out.println(sb.length());
}
/**
* 主测试方法
* @param args
*/
public static void main(String[] args) {
try {
ThreadTest.testStringBuffer();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
上面,我们起10个线程,每个线程循环1000次往StringBuffer对象里面append字符。理论上应该输出实例sb的字符串长度=10000,我们执行代码,实际输出也是10000,业务上证明了StringBuffer是线程安全的。
2、验证StringBuilder是线程不安全的
/**
* 验证StringBuild线程不安全,如下,如果length!=1000,则可证明
* @throws InterruptedException
*/
public static void testStringBuild() throws InterruptedException {
StringBuilder sb = new StringBuilder();
for (int i=0; i<10; i++){
new Thread(new Runnable() {
@Override
public void run() {
for (int j=0; j<1000; j++){
sb.append("a");
}
}
}).start();
}
Thread.sleep(100);
System.out.println(sb.length());
}
/**
* 主测试方法
* @param args
*/
public static void main(String[] args) {
try {
ThreadTest.testStringBuild();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
上面,我们也起10个线程,每个线程循环1000次往StringBuilder对象里面append字符。我们也希望像StringBuffer一样,得到10000的结果,但是我们运行代码后,结果却<10000。业务上证明了StringBuilder是线程不安全的。
三、源码分析
我们通过查看StringBuffer和StringBuilder的append()方法,发现他们都调用父类AbstractStringBuilder的append()方法,分别如下:
StringBuffer重写的append方法:
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
StringBuilder重写的append方法:
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
父类AbstractStringBuilder的append()方法:
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
通过上面的源码,可知,StringBuffer和StringBuilder的append()的区别就是StringBuffer多了个 toStringCache = null; 这里,我们不再分析AbstractStringBuilder的append()方法的实现方式,大家可以自行了解,下面主要分析影响线程安全性的这段代码的作用。
我们查看StringBuffer源码,发现多了比StringBuilder多了一个参数:
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{
/**
* A cache of the last value returned by toString. Cleared
* whenever the StringBuffer is modified.
*/
private transient char[] toStringCache;
//其他代码,略
}
再看下StringBuffer的toString方法:
@Override
public synchronized String toString() {
if (toStringCache == null) {
toStringCache = Arrays.copyOfRange(value, 0, count);
}
return new String(toStringCache, true);
}
这里的作用就是如果StringBuffer对象此时存在toStringCache,在多次调用其toString方法时,其new出来的String对象是会共享同一个char[] 内存的,达到共享的目的。但是StringBuffer只要做了修改,其toStringCache属性值都会置null处理。这也是StringBuffer和StringBuilder的一个区别点,也是StringBuffer为什么线程安全的原因。
四、结论
通过上面的举例和源码分析,我们可以知道为什么StringBuffer是线程安全的,StringBuilder是线程不安全的,至于他们的append()方法,都是集成父类的append()方法,大家可以网上去了解下具体实现原理。