再谈StringBuilder和StringBuffer的区别

通常字符串的拼接我们可能会用加号"+"拼接,或者用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。

猜你喜欢

转载自blog.csdn.net/timchen525/article/details/80672933