StringBuilder & StringBuffer

김코·2025년 9월 9일

설명

자바에서 String은 불변의 객체이다.
따라서 String을 이용해 문자열 연산이 잦으면 힙 메모리에 많은 중간 객체가 생성 및 제거되고 이는 성능 저하로 이어진다.

따라서 변하지 않은 문자열을 저장할 때 적합하다.

StringBuilder와 StringBuffer는 이를 커버할 수 있다.

  • StringBuilder
    비동기 방식이기에 Single Thread 환경에서 변화되는 문자열에 사용한다.
    비동기 방식이기에 처리 속도는 빠르다.

  • StringBuffer
    동기 방식으로 저장되기에 Multi Thread 환경에서 문자열이 변경될 경우 사용한다.

StringBuilder를 멀티 스레드 환경에서 사용하면 어떻게 될까?

망한다.
StringBuilder에서 문자열이 추가될 때, 즉 append() 메서드가 실행되면 그 방식은 다음과 같다.
1. 현재 문자열 길이 확인
2. 추가할 문자열 공간 확인
3. 공간 부족? -> 내부 크기 확장
4. 새로운 문자열 추가
5. 전체 길이 업데이트

StringBuilder의 문제: 이 과정을 한 스레드가 끝내지 못했다면 다른 스레드가 중간에 들어올 수 있다.
그러면 덮어쓸 수도, 뒤섞일 수 있는거다.

이런 개념은 알고 있었지만 실제 코드로 딱히 쳐본적이 없었기에 둘의 차이를 보기 위해 코드로 간편하게 구현해봤다.

코드


public class Main {

    public static void main(String[] args) throws InterruptedException {

        // 1. StringBuilder 테스트 - 여러 스레드에서 동시에 접근할 때 안전하지 않다
        StringBuilder sb = new StringBuilder();

        Runnable stringBuilderTask = () -> {
            for (int i = 0; i < 1000; i++) {
                sb.append("A");
            }
        };

        Thread[] stringBuilderThreads = new Thread[10];
        for (int i = 0; i < 10; i++) {
            stringBuilderThreads[i] = new Thread(stringBuilderTask);
            stringBuilderThreads[i].start();
        }

        for (int i = 0; i < 10; i++) {
            stringBuilderThreads[i].join();
        }

        System.out.println("StringBuilder의 결과 길이: " + sb.length());



        // 2. StringBuffer 테스트 - 여러 스레드에서 동시에 접근할 때 안전하다
        StringBuffer sbf = new StringBuffer();

        Runnable stringBufferTask = () -> {
            for (int i = 0; i < 1000; i++) {
                sbf.append("A");
            }
        };

        Thread[] stringBufferThreads = new Thread[10];
        for (int i = 0; i < 10; i++) {
            stringBufferThreads[i] = new Thread(stringBufferTask);
            stringBufferThreads[i].start();
        }

        for (int i = 0; i < 10; i++) {
            stringBufferThreads[i].join();
        }

        System.out.println("StringBuffer의 결과 길이: " + sbf.length());

    }
}

첫 번째 실행 결과

StringBuilder의 결과 길이: 4009
StringBuffer의 결과 길이: 10000

두 번째 실행 결과

Exception in thread "Thread-0" java.lang.ArrayIndexOutOfBoundsException: arraycopy: last destination index 1151 out of bounds for byte[34]
StringBuilder의 결과 길이: 8799
StringBuffer의 결과 길이: 10000

StringBuilder의 값은 그때마다 다르고, 길이 계산 오류로 인한 예외도 터진다.

즉, StringBuilder는 다른 스레드에 의해 일관성이 깨지는 위험이 있는 것이다.

profile
백엔드 공부하는 코린이입니다

0개의 댓글