CATEGORY

카테고리 (607)
AI (12)
Language & Specs (259)
FrameWork (34)
Library (19)
App (41)
Git (8)
Build & Dependency (2)
AWS (15)
DataBase (45)
OS (33)
Tool (16)
IT (120)
반응형
SEEMINGLY ONLINE

Seemingly
Online

이모저모 방방곡곡 두루두루 개발지식 저장소

RECENT POSTS

Language & Specs/Java

자바 StringBuilder vs StringBuffer 차이 — 스레드 안전성·성능 한 번에 정리

반응형

StringBuilder와 StringBuffer의 차이는 딱 하나, 스레드 안전성(동기화)입니다. StringBuffer는 메서드가 synchronized라 여러 스레드가 동시에 써도 안전하지만 그만큼 느리고, StringBuilder는 동기화를 하지 않아 단일 스레드에서 더 빠릅니다(Java 21 기준 약 2~3배). 나머지 메서드(append·insert·delete 등)와 사용법은 완전히 같습니다. 결론부터: 대부분의 코드는 StringBuilder를 쓰면 됩니다. 오라클 공식 문서도 그렇게 권합니다.

1. 한눈에 보는 차이

항목 StringBuilder StringBuffer
동기화(스레드 안전) ❌ 안 함 ✅ 메서드가 synchronized
속도(단일 스레드) 빠름 느림(동기화 비용)
등장 시점 JDK 5 (2004) JDK 1.0
API(메서드) 동일 — 둘 다 append/insert/delete/reverse/toString …
언제 쓰나 기본값(대부분) 하나의 인스턴스를 여러 스레드가 공유할 때

오라클 Java 21 공식 문서(StringBuffer)는 이렇게 못 박습니다: "As of release JDK 5, this class has been supplemented with an equivalent class designed for use by a single thread, StringBuilder. The StringBuilder class should generally be used in preference to this one, as it supports all of the same operations but it is faster, as it performs no synchronization." — 즉 같은 기능인데 동기화를 안 해서 더 빠르니, 웬만하면 StringBuilder를 써라는 뜻입니다.

2. String을 += 로 이어붙이면 왜 안 되나

두 클래스를 이해하려면 먼저 String과의 관계를 알아야 합니다. String은 불변(immutable)이라 +로 문자열을 이어붙일 때마다 새 String 객체가 계속 생깁니다. 반복문 안에서 String을 누적하면 객체가 수천·수만 개 만들어져 느려집니다. 그래서 변경 가능한 버퍼인 StringBuilder/StringBuffer를 씁니다.

// ❌ 반복문에서 String += : 매번 새 객체 생성
String s = "";
for (int i = 0; i < 10000; i++) s += i;   // 느림

// ✅ StringBuilder : 하나의 버퍼에 누적
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) sb.append(i);
String result = sb.toString();
💡 참고: 요즘 컴파일러는 a + b + c처럼 한 줄짜리 문자열 연결은 내부적으로 StringBuilder(또는 JDK 9+의 invokedynamic 방식)로 바꿔 줍니다. 문제가 되는 건 반복문 안에서 누적할 때입니다.

3. 스레드 안전성 차이 — 직접 돌려보면 이렇게 다릅니다

말로만 "StringBuffer는 스레드 안전"이라고 하면 와닿지 않으니 실제로 돌려봤습니다. 10개 스레드가 각각 같은 버퍼에 append를 1만 번씩 호출합니다. 기대 길이는 10 × 10000 = 100,000이어야 합니다.

int threads = 10, per = 10000;
StringBuilder builder = new StringBuilder();
StringBuffer  buffer  = new StringBuffer();

// 10개 스레드가 같은 버퍼에 동시에 append
run(threads, per, () -> builder.append('x'));
run(threads, per, () -> buffer.append('x'));

System.out.println("기대 길이 = " + (threads * per));   // 100000
System.out.println("StringBuilder = " + builder.length());
System.out.println("StringBuffer  = " + buffer.length());

Java 21(21.0.1)에서 실행한 실제 출력입니다:

기대 길이 = 100000
StringBuilder = 60415     ← 데이터 유실! (실행할 때마다 값이 다름)
StringBuffer  = 100000    ← 정확함

StringBuilder는 동기화가 없어 여러 스레드가 동시에 쓰면 서로 덮어써서 데이터가 사라집니다. 위에서는 40%가 유실됐고, 이 값은 실행할 때마다 달라집니다(운 나쁘면 ArrayIndexOutOfBoundsException이 터지기도 합니다). 반면 StringBuffer는 항상 정확히 100,000입니다. 이렇게 여러 스레드가 하나의 인스턴스를 공유할 때만 StringBuffer의 값이 있습니다. 동기화가 왜 필요한지는 SimpleDateFormat thread safe 문제 글에서도 같은 맥락으로 다뤘습니다.

4. 성능 차이 — 얼마나 느린가

단일 스레드에서 append를 1,000만 번 반복해 시간을 쟀습니다(Java 21, 같은 머신). 수치는 환경마다 다르지만 경향은 분명합니다.

StringBuilder 10,000,000회 append: 63 ms
StringBuffer  10,000,000회 append: 173 ms   ← 약 2.7배 느림

StringBuffer는 append 한 번마다 락을 잡았다 풉니다. 단일 스레드에서는 그 락이 전혀 쓸모없는데도 비용만 냅니다. 그래서 혼자 쓰는 버퍼라면 StringBuilder가 정답입니다.

5. 그래서 뭘 써야 하나

기본은 StringBuilder. 메서드 안의 지역 변수로 문자열을 만드는 대부분의 경우가 여기 해당합니다. 그 버퍼는 한 스레드만 씁니다.
StringBuffer는 "하나의 인스턴스를 여러 스레드가 공유"할 때만. 실무에선 드뭅니다.

한 가지 짚을 점: StringBuffer를 쓴다고 자동으로 안전해지는 게 아닙니다. 보장되는 건 append 같은 메서드 하나하나일 뿐, if (sb.length() == 0) sb.append(x)처럼 여러 호출을 묶은 로직은 그 사이에 다른 스레드가 끼어들 수 있어 여전히 별도 동기화가 필요합니다. 결국 공유 상태를 제대로 다루려면 synchronized나 동시성 자료구조를 직접 써야 하고, 그러면 StringBuffer의 이점은 더 줄어듭니다.

자주 묻는 질문 (FAQ)

Q. String, StringBuilder, StringBuffer 셋 중 뭘 쓰나요?
문자열을 바꾸지 않으면 String, 반복문 등에서 조립하면 StringBuilder, 그 버퍼를 여러 스레드가 공유하면 StringBuffer입니다. 실무 대부분은 앞 둘로 끝납니다.

Q. StringBuilder와 StringBuffer는 메서드가 다른가요?
아니요. 둘 다 같은 부모(AbstractStringBuilder)에서 온 동일한 APIappend·insert·delete·reverse·toString이 똑같습니다. 코드에서 클래스 이름만 바꿔도 그대로 컴파일됩니다. 차이는 오직 동기화 여부입니다.

Q. 그럼 StringBuffer는 이제 안 써도 되나요?
공유 버퍼가 아니라면 쓸 이유가 없습니다. 오라클 문서도 "StringBuilder를 우선 쓰라"고 권합니다. 다만 오래된 코드나 스레드 공유가 진짜 필요한 곳에서는 여전히 쓰입니다.

마무리

정리하면 — StringBuilder = 빠름·비동기화(기본값), StringBuffer = 느림·동기화(공유될 때만). API는 완전히 같으니 고민은 "이 버퍼를 여러 스레드가 나눠 쓰는가?" 하나면 됩니다. 아니라면 StringBuilder. 정확한 시그니처와 동기화 설명은 오라클 공식 Javadoc에서 확인하세요.


📚 참고 출처 (2026년 7월 20일 확인)
· Oracle Java 21 Javadoc — StringBuffer
· Oracle Java 21 Javadoc — StringBuilder
· 본문 스레드 안전성·성능 수치는 Java 21(21.0.1)에서 직접 실행해 확인.

반응형

COMMENTS