通常字符串的拼接我们可能会用加号"+"拼接,或者用StringBuilder、StringBuffer进行拼接。已经知道加号"+"拼接的字符串可能会创建多个对象(注意:我用的是可能,因为在一两个的拼接时候,jdk编译器会帮我们优化,而没有创建多个对象,不过,对于for循环里面的字符串拼接,推荐用StringBuilder或者StringBuffer)。
区别:StringBuilder是
线程不安全的,而StringBuffer是
线程安全的。
测试栗子如下:
1、利用StringBuilder来拼接5000个单个字符,预计拼接后的长度为5000,如果不是5000,则不是线程安全的。
@Slf4j
public class StringExample1 {
// 请求总数
public static int clientTotal = 5000;
// 同时并发执行的线程数
public static int threadTotal = 200;
public static StringBuilder stringBuilder = new StringBuilder();
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i< clientTotal; i ++) {
executorService.execute(()->{
try {
semaphore.acquire();
update();
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
log.info("size:{}", stringBuilder.length());
}
public static void update() {
stringBuilder.append("i");
}
}
输出:
[main] INFO com.example.concurrent.commonUnsafe.StringExample1 - size:4963
1、利用
StringBuffer来拼接5000个单个字符,预计拼接后的长度为5000,如果不是5000,则不是线程安全的。
@Slf4j
public class StringExample2 {
// 请求总数
public static int clientTotal = 5000;
// 同时并发执行的线程数
public static int threadTotal = 200;
public static StringBuffer stringBuffer = new StringBuffer();
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i< clientTotal; i ++) {
executorService.execute(()->{
try {
semaphore.acquire();
update();
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
log.info("size:{}", stringBuffer.length());
}
public static void update() {
stringBuffer.append("i");
}
}
输出:
[main] INFO com.example.concurrent.commonUnsafe.StringExample2 - size:5000
分析:StringBuilder和StringBuffer内部实现机制的不同:
StringBuffer的所有方法都加了Synchronized关键字,比如:
stringBuffer
.append(
"i"
);
这里面的append方法的源码为:
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
总结:
既然StringBuilder是线程不安全的,为什么还要使用StringBuilder,很明显线程安全的StringBuffer是同synchronized关键字来保证同一时间只能有一个线程调用该方法,因此在性能上是有损耗的。如果不是在并发情况下,我们通常字符串的拼接是在一个方法内部的局部变量(局部变量是堆栈封闭的,是线程安全的),因此,此时选择StringBuilder的性能更高。
即
局部变量用StringzBuilder,
全局变量用StringBuilder。