一、前言
可能大家在学习java的基础过程中,都知道StringBuilder相对StringBuffer效率更高,但是线程不安全。可是,到底是怎么个不安全法,反正我是懵的。借此机会,写个小代码测试下。
二、编码
既然是测试StringBuilder和StringBuffer的线程安全性,那就分别new一个StringBuilder和StringBuffer作为共享变量,传递到多个线程进行操作,看看最后结果如何。
package com.cjt.test;public class Test { public static void main(String[] args) { StringBuilder builder = new StringBuilder(); StringBuffer buffer = new StringBuffer(); for (int i = 0; i < 10; i++) { new A(builder, buffer).start(); } } }class A extends Thread { private StringBuilder builder; private StringBuffer buffer; A(StringBuilder builder, StringBuffer buffer) { this.buffer = buffer; this.builder = builder; } @Override public void run() { for (int i = 0; i < 100; i++) { builder.append("c"); buffer.append("c"); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("[" + Thread.currentThread().getName() + "]builder:" + builder.length()); System.out.println("[" + Thread.currentThread().getName() + "]buffer:" + buffer.length()); } }
三、分析测试
我在run()里面加了一个Thread.sleep(10)延时,为了更好地体现多个线程操作同一个变量的安全性问题。运行结果:
[Thread-9]builder:939[Thread-5]builder:939[Thread-9]buffer:994[Thread-5]buffer:994[Thread-1]builder:941[Thread-1]buffer:996[Thread-4]builder:941[Thread-4]buffer:996[Thread-0]builder:941[Thread-0]buffer:996[Thread-8]builder:941[Thread-8]buffer:996[Thread-2]builder:942[Thread-2]buffer:998[Thread-6]builder:942[Thread-6]buffer:998[Thread-3]builder:944[Thread-3]buffer:1000[Thread-7]builder:944[Thread-7]buffer:1000
说实话当我运行后也是一头问号?什么鬼,没有任何规律可寻。的确是这样,但是经过多次运行后可以总结一点是:StringBuffer最终的length总是1000,但是StringBuilder的length总是少于1000。
这也太没说服力了~!那就要分别看看它们的append()源码了;
StringBuilder:
public StringBuilder append(String str) { super.append(str); return this; }
StringBuffer:
public synchronized StringBuffer append(String str) { super.append(str); return this; }
看样子好像就隔只一个synchronized关键字,那就看看super.append(str)的源码吧,但可以吃惊地发现都是AbstractStringBuilder.append(String str),看看这段代码:
public AbstractStringBuilder append(String str) { if (str == null) str = "null"; int len = str.length(); ensureCapacityInternal(count + len); str.getChars(0, len, value, count); count += len; return this; }
由于底层字符串都是由char数组实现的,这里str.getChars(0, len, value, count);就是一个明显的赋值操作。因而可以说StringBuilder和StringBuffer的append(String str)方法都是通过getChars方法来实现字符串拼接的。
有人看到上面还有个ensureCapacityInternal(count + len);调用,这个只是一个底层的char数组扩容计算,有兴趣的可以自己私下看看源码,这里就不贴出来。
那我们就一探getChars方法的究竟:
public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin){ if (srcBegin < 0) throw new StringIndexOutOfBoundsException(srcBegin); if ((srcEnd < 0) || (srcEnd > count)) throw new StringIndexOutOfBoundsException(srcEnd); if (srcBegin > srcEnd) throw new StringIndexOutOfBoundsException("srcBegin > srcEnd"); System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin); }
实质上是调用底层System.arraycopy()方法实现的,可能你觉得我扯远了,其实,的确有点吧。
StringBuilder中的append方法没有使用synchronized关键字,意味着多个线程可以同时访问这个方法。那么问题就来了额,如果两个线程同时访问到这个方法,那么AbstractStringBuilder中的count是不是就是相同的,所以这两个线程都是在底层char数组的count位置开始append添加,那么最终的结果肯定就是在后执行的那个线程append进去的数据会将前面一个覆盖掉。因此我们的控制台输出才会出现StringBuilder一直都是小于1000的。然而StringBuffer却不会发生这种情况。
三、总结
- StringBuilder相比StringBuffer效率更高,但多线程不安全;
- 在单线程中字符串的频繁拼接使用StringBuilder效率更高,对于多线程使用StringBuffer则更安全;
- 字符串简单操作时没必要使用上述两者,还是用String类型提高速度;
登录 | 立即注册