String是不可变的,每次对字符串进行拼接都会创建一个新的String对象,这可能导致频繁的内存分配和垃圾回收。为了避免这种情况,我们可以使用StringBuilder(如果需要线程安全性用StringBuffer)来执行大量的字符串拼接操作。
StringBuilder虽然在字符串拼接时,性能已经比String拼接优异很多了,但我们依然可以从三个方面来对StringBuilder进行性能优化。
- StringBuilder默认容量是16,当我们拼接多个字符串,会进行扩容以及复制字符数组操作。
- 当高并发,频繁地创建StringBuilder对象也是会导致频繁的内存分配和垃圾回收。
- StringBuilder本身是线程不安全的,我们可以通过ThreadLocal,来让它进行线程隔离。
StringBuilder扩容代码:
对StringBuilder进行优化,提出一个StringBuilderHelper类,提供优化后的接口。
public class StringBuilderHelper {
//由于StringBuilder不是线程安全的,用ThreadLocal来进行线程隔离
private static ThreadLocal<StringBuilder> stringBuilderLocal = new ThreadLocal<StringBuilder>() {
@Override
protected StringBuilder initialValue() {
//根据业务需求,给定一个合适的初始capacity
//尽量避免后续扩容
//也避免给太大容量,浪费空间
return new StringBuilder(4096);
}
};
public static StringBuilder getBuilder(){
StringBuilder stringBuilder = stringBuilderLocal.get();
if(stringBuilder.capacity() > 16384){
//当StringBuilder已经扩容到了一定值(这个值根据业务来定)
//使用一个新的初始容量的StringBuilder
stringBuilder = new StringBuilder(4096);
//更新ThreadLocal
stringBuilderLocal.set(stringBuilder);
}else{
//重置length,实现对StringBuilder的复用
stringBuilder.setLength(0);
}
return stringBuilder;
}
}
测试代码:
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(2);
Thread thread1 = new Thread(() -> {
try {
cyclicBarrier.await();//保证两个线程同时执行
System.out.println("Thread1开始执行");
//线程间StringBuilder隔离
StringBuilder stringBuilder = StringBuilderHelper.getBuilder();
stringBuilder.append("a").append("c").append("b");
System.out.println("Thread1:" + stringBuilder.toString());
//线程内StringBuilder复用
stringBuilder = StringBuilderHelper.getBuilder();
stringBuilder.append("d").append("e").append("f");
System.out.println("Thread1:" + stringBuilder.toString());
} catch (Exception e) {
}
});
Thread thread2 = new Thread(() -> {
try {
cyclicBarrier.await();//保证两个线程同时执行
System.out.println("Thread2开始执行");
//线程间StringBuilder隔离
StringBuilder stringBuilder = StringBuilderHelper.getBuilder();
stringBuilder.append("o").append("p").append("q");
System.out.println("Thread2:" + stringBuilder.toString());
//线程内StringBuilder复用
stringBuilder = StringBuilderHelper.getBuilder();
stringBuilder.append("r").append("s").append("t");
System.out.println("Thread2:" + stringBuilder.toString());
} catch (Exception e) {
}
});
thread1.start();
thread2.start();
}
测试结果:
通过结果可以看出,不用重复地去new StringBuilder,就能实现StringBuilder线程内复用,线程间隔离。