首先明确几点结论:

  1. 可变性:就String和后两者相比,String是字符串常量,后两者是字符串变量。
  2. 线程安全性:就后两者相比,StringBuilder不是线程安全的,而StringBuffer是线程安全的。
  3. 性能:就效率来说,通常情况下:StringBuilder>StringBuffer>String。

分析

一、可变性

虽然都是通过一个char数组来存储数据,但是String的char数组是final修饰的,因此是不可变的。而后两者类都是继承自AbstractStringBuilder,它的char数组没有声明为final类型,是可变的。

二、线程安全性

在这一节,我们可以通过一个对比实验来说明为什么说StringBuilder不是线程安全的,而StringBuffer是线程安全的

2.1 StringBuilder不是线程安全的

 首先给出代码

StringBuilder stringBuilder = new StringBuilder();
		for(int i =0; i < 10; i++) {
			new Thread(new Runnable() {
				
				@Override
				public void run() {
					// TODO Auto-generated method stub
					for(int j = 0;j < 1000; j++) {
						stringBuilder.append("a");
					}
					
				}
			}).start();
		}
		Thread.sleep(1000);
		System.out.println(stringBuilder.length());

 在这段代码中,我们通过创建10个线程,每个线程通过StringBuilder的append的操作向字符串追加1000个字符,最后统计字符串长度。理论上的长度应该是10*1000=10000才对。

但是我们看结果

String线程安全 java stringbuilder线程安全_数组

抛出了一个ArrayIndexOutOfBoundsException异常,并且字符串长度为7824。而且跑多次会发现异常不是必现的。

1、我们来分析问什么长度不是10000。

我们来看StringBuilder的append方法:

String线程安全 java stringbuilder线程安全_字符串_02

其继承了父类的方法,那我们去看一下父类AbstractStringBuilder的append方法

String线程安全 java stringbuilder线程安全_字符串_03

在这个方法中我们可以看到有一个count+=len,len是追加的长度。假设两个线程同时执行到这一行,拿到的count都是10,要增加的值len都为1,那么两个线程分别对其加1,将数据赋值给count,最终两个线程执行完,得到的结果是11,而不是12。因此我们给出的实验结果不是10000。

2、为什么会抛出不必现的ArrayIndexOutOfBoundsException异常

同样我们继续分析上面append方法中的ensureCapacityInternal()方法,其表示判断char数组能否盛的下新的字符串,传入的参数表示即将生成的新字符串长度,value表示char数组。如果不能盛下,则调用expandCapacity()方法进行扩容。

String线程安全 java stringbuilder线程安全_数组_04

而扩容则是通过new一个新的数组实现,其长度为原数组长度两倍再加2,然后通过System.arrayCopy()将原数组内容拷贝到新的数组,最后将value引用指向该数组。

String线程安全 java stringbuilder线程安全_java_05

Arrays.copyOf()实际上便是生成新数组的过程。

String线程安全 java stringbuilder线程安全_String线程安全 java_06

ensureCapacityInternal()方法做完了扩容,str.getChar()方法便是追加字符的操作。

String线程安全 java stringbuilder线程安全_java_07

假设value长度为6,而两个线程同时执行到ensureCapacityInternal()方法时count=5,因为增加的字符len=1,所以两个线程都不会进行扩容。假设线程一执行完str.getChar()方法,时间片用完,此时count变成了6,当线程二执行str.getChar()时则会出现这种越界的异常。

2.2 StringBuffer是线程安全的

String线程安全 java stringbuilder线程安全_字符串_08

得到的结果是10000。

String线程安全 java stringbuilder线程安全_字符串_09

当我们观察StringBuffer的append方法时,可以看到该方法使用了synchronized关键字,这就是StringBuffer线程安全的原因了。

String线程安全 java stringbuilder线程安全_java_10

三、性能

因为String的值不能改变,因此都在对其操作时,实际是让其引用指向一个新的字符串地址。因此相对来说,空间和时间上的开销都有所浪费。而相对于StringBuilder来说,StringBuffer增加了线程安全性,因此性能上也有所降低。