引言
很多人都知道String是不可变的,StringBuffer和StringBuilder是可变的,那么为什么呢?
首先我们确定一个概念性问题,什么是不可变对象!
什么是不可变对象:如果一个对象,在它创建完成之后,不能再改变它的状态,那么这个对象就是不可变的。不能改变状态的意思是,不能改变对象内的成员变量,包括基本数据类型的值不能改变,引用类型的变量不能指向其他的对象,引用类型指向的对象的状态也不能改变。
关于Spring是不可变的可参考:Spring为什么是不可变的
StringBuffer和StringBuilder的区别
- 继承关系
由图可知,StringBuffer和StringBuilder都继承了AbstractStringBuilder - 线程安全
这是它们的主要区别,主要在于一个关键字,synchronized
看过StringBuffer源码应该会知道, 几乎都是所有方法都加了synchronized。 用synchronized关键字修饰意味着什么?加锁,资源同步串行化处理,所以是线程安全的。
而看过StringBuilder源码可知,基本上所有方法都没有用synchronized关键字修饰,当多线程访问时,就会出现线程安全性问题。
为了证明StringBuffer线程安全,StringBuilder线程不安全,我们通过一段代码进行验证:
测试思想
分别用1000个线程写StringBuffer和StringBuilder,
使用CountDownLatch保证在各自1000个线程执行完之后才打印StringBuffer和StringBuilder长度,观察结果。
测试代码
@Test
public void testStringBufferAndStringBuiler(){
//证明StringBuffer线程安全,StringBuilder线程不安全
StringBuffer stringBuffer = new StringBuffer();
StringBuilder stringBuilder = new StringBuilder();
CountDownLatch latch1 = new CountDownLatch(1000);
CountDownLatch latch2 = new CountDownLatch(1000);
for (int i = 0; i < 1000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
stringBuilder.append(1);
} catch (Exception e) {
e.printStackTrace();
} finally {
latch1.countDown();
}
}
}).start();
}
for (int i = 0; i < 1000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
stringBuffer.append(1);
} catch (Exception e) {
e.printStackTrace();
} finally {
latch2.countDown();
}
}
}).start();
}
try {
latch1.await();
System.out.println(stringBuilder.length());
latch2.await();
System.out.println(stringBuffer.length());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
测试结果
* StringBuffer不论运行多少次都是1000长度。
* StringBuilder绝大多数情况长度都会小于1000。
* StringBuffer线程安全,StringBuilder线程不安全得到证明。
总结
- StringBuffer和StringBuilder都继承自抽象类AbstractStringBuilder。
- 对于拼接字符串效率要比String要高:存储数据的字符数组没有被final修饰,说明值可以改变,内部可自动扩容
- 线程安全性:StringBuffer效率低,线程安全,因为StringBuffer中很多方法都被 synchronized 修饰了,多线程访问时,线程安全,但是效率低下,因为它有加锁和释放锁的过程。StringBuilder效率高,但是线程是不安全的。