淺談 String StringBuilder StringBuffer 之效能和執行緒安全

2020-09-19 12:04:47

關注微信公眾號 架構技術之美 ,學習更多技術和學習資料。

一、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。