面试经常会问到,StringBuilder和StringBuffer的区别,大家都回答StringBuilder是线程不安全的,StringBuffer是线程安全的,那么StringBuilder到底是哪里不安全了呢?
public class DemoUtil { public static void main(String[] args) throws Exception{ StringBuilder stringBuilder = new StringBuilder(); for(int j=0;j<10;j++){ new Thread(new Runnable() { @Override public void run() { for(int i=0;i<100;i++){ stringBuilder.append("2"); } } }).start(); } Thread.sleep(100); System.out.println(stringBuilder.length()); } }
如上面所写的例子,创建了10个线程,每个线程循环100次,向stringBuilder中添加100个‘2’,如果结果正常的话,stringBuilder的长度应该是10000,但是你可以自己执行下,一般结果都是小于10000的,如果StringBuilder 换成StringBuffer的话,结果正好是10000。从这个例子的结果就可以看出StringBuilder是线程不安全的,那么到底哪里不安全了呢?
进入StringBuilder的append()方法,
public StringBuilder append(String str) { super.append(str); return this; }
也就是说StringBuilder的append方法是继承来的,那么它继承了谁呢?
public final class StringBuilder extends AbstractStringBuilder implements java.io.Serializable, CharSequence
StringBuilder继承了AbstractStringBuilder,我们再来看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; }
从count += len;这行代码我们就可以看到他肯定不是一个原子操作,这是由2步组合成的,我们都知道线程不安全一般都是因为多个线程共享变量导致的,那么这里的不安全到底是count变量造成的,还是len这个变量造成的呢?还是说这2个变量都会导致线程安全问题呢?
其实这里的线程不安全是由count这个变量造成的,len这个变量不会导致线程不安全,为什么呢?
因为我们可以看到len这个变量是在append方法内定义的,方法内部定义的变量是在运行时动态生成的。每个线程都有一个自己的堆栈,用于保存运行时的数据。每次调用时,变量都是在运行时堆栈上保存的,方法结束变量也就释放了。因此len变量是不会被多个线程共享的。
再来看count 变量的定义,count是AbstractStringBuilder的成员变量,能够被多个线程共享。因此
AbstractStringBuilder的线程不安全问题就出在count这个变量上。
那么StringBuffer又是怎么保证线程安全的呢?
@Override public synchronized StringBuffer append(String str) { toStringCache = null; super.append(str); return this; }
直接在这个append的方法上加了synchronized锁,因此保证了线程安全。