浅谈 String StringBuilder StringBuffer 之性能和线程安全

关注微信公众号 架构技术之美 ,学习更多技术和学习资料。

一、String 字符串常量

String 是不可变对象,每次对 String 对象进行改变都会生成一个新的 String 对象,然后将指针指向新的 String 对象,故经常改变内容的字符串最好不要用 String 。因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以后, JVM 的 GC 就会开始工作,那速度就更加慢了。

package com.nobody.part01;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

/**
 * @author Mr.nobody
 * @Description 测试String字符串拼接性能
 * @date 2020/9/17
 */
public class StringDemo {
    
    

    // 请求总数
    private static int CLIENT_COUNT = 10000;
    // 并发线程数
    private  static int THREAD_COUNT = 500;
    // 全局变量
    private static String STRING = new String();

    public static void main(String[] args) throws InterruptedException {
    
    
        // 固定线程池
        ExecutorService executorService = Executors.newFixedThreadPool(CLIENT_COUNT);
        // 控制同一个时刻,只能有多少个线程同时运行指定代码,即acquire和release之间的代码
        Semaphore semaphore = new Semaphore(THREAD_COUNT);
        CountDownLatch countDownLatch = new CountDownLatch(CLIENT_COUNT);
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < CLIENT_COUNT; i++) {
    
    
            executorService.execute(() -> {
    
    
                try {
    
    
                    semaphore.acquire();
                    STRING += "0";
                    semaphore.release();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                } finally {
    
    
                    countDownLatch.countDown();
                }
            });
        }
        // 等待所有请求执行完
        countDownLatch.await();
        // 关闭线程池
        executorService.shutdown();
        long endTime = System.currentTimeMillis();
        System.out.println("String -- 总共耗时:" + (endTime - startTime) + "ms,字符串长度:" + STRING.length());
    }
}

在这里插入图片描述

二、StringBuffer 字符串变量

StringBuffer 是可变对象,每次对 StringBuffer 对象进行改变都会对StringBuffer 对象本身进行操作。而不是生成新的对象,再改变对象引用。如果 StringBuffer 对象在多线程环境下,特别是字符串对象经常改变的情况下,推荐使用它 。因为 StringBuffer 几乎所有的方法都加了synchronized关键字,所以是线程安全的,但是性能会相对较差。

package com.nobody.part01;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

/**
 * @author Mr.nobody
 * @Description 测试线程安全类StringBuffer
 * @date 2020/9/17
 */
public class StringBufferDemo {
    
    

    // 请求总数
    private static int CLIENT_COUNT = 10000;
    // 并发线程数
    private  static int THREAD_COUNT = 500;
    // 全局变量,线程安全StringBuffer
    private static StringBuffer STRINGBUFFER = new StringBuffer();

    public static void main(String[] args) throws InterruptedException {
    
    
        // 固定线程池
        ExecutorService executorService = Executors.newFixedThreadPool(CLIENT_COUNT);
        // 控制同一个时刻,只能有多少个线程同时运行指定代码,即acquire和release之间的代码
        Semaphore semaphore = new Semaphore(THREAD_COUNT);
        CountDownLatch countDownLatch = new CountDownLatch(CLIENT_COUNT);
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < CLIENT_COUNT; i++) {
    
    
            executorService.execute(() -> {
    
    
                try {
    
    
                    semaphore.acquire();
                    STRINGBUFFER.append("0");
                    semaphore.release();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                } finally {
    
    
                    countDownLatch.countDown();
                }
            });
        }
        // 等待所有请求执行完
        countDownLatch.await();
        // 关闭线程池
        executorService.shutdown();
        long endTime = System.currentTimeMillis();
        System.out.println("StringBuffer -- 总共耗时:" + (endTime - startTime) + "ms,字符串长度:" + STRINGBUFFER.length());
    }
}

在这里插入图片描述

三、StringBuilder 字符串变量

StringBuilder 一个可变的字符序列。StringBuilder 提供一个与 StringBuffer 兼容的 API,但不保证同步(即线程不安全)。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。在堆栈封闭等线程安全的环境下(方法中的局部变量)应该首选 StringBuilder。

package com.nobody.part01;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

/**
 * @author Mr.nobody
 * @Description 测试线程不安全类StringBuilder
 * @date 2020/9/17
 */
public class StringBuilderDemo {
    
    

    // 请求总数
    private static int CLIENT_COUNT = 10000;
    // 并发线程数
    private  static int THREAD_COUNT = 500;
    // 全局变量,线程不安全StringBuilder
    private static StringBuilder STRINGBUILDER = new StringBuilder();

    public static void main(String[] args) throws InterruptedException {
    
    
        // 固定线程池
        ExecutorService executorService = Executors.newFixedThreadPool(CLIENT_COUNT);
        // 控制同一个时刻,只能有多少个线程同时运行指定代码,即acquire和release之间的代码
        Semaphore semaphore = new Semaphore(THREAD_COUNT);
        CountDownLatch countDownLatch = new CountDownLatch(CLIENT_COUNT);
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < CLIENT_COUNT; i++) {
    
    
            executorService.execute(() -> {
    
    
                try {
    
    
                    semaphore.acquire();
                    STRINGBUILDER.append("0");
                    semaphore.release();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                } finally {
    
    
                    countDownLatch.countDown();
                }
            });
        }
        // 等待所有请求执行完
        countDownLatch.await();
        // 关闭线程池
        executorService.shutdown();
        long endTime = System.currentTimeMillis();
        System.out.println("StringBuilder -- 总共耗时:" + (endTime - startTime) + "ms,字符串长度:" + STRINGBUILDER.length());

    }
}

在这里插入图片描述

四、总结

  • 对于不可变字符串或者字符串内容改变少的情况,推荐 String。
  • 字符串对象可能会被多线程访问,并且经常改变内容,推荐 StringBuffer。
  • 在堆栈封闭等线程安全的环境下(方法中的局部变量)推荐 StringBuilder。

猜你喜欢

转载自blog.csdn.net/chenlixiao007/article/details/108654332