Java에서 문자열을 다루는 대표적인 클래스로는 String, StringBuffer, StringBuilder가 있습니다. 작은 시스템에서는 대부분 String을 사용해도 큰 문제가 발생하지 않지만, 대규모 서비스와 시스템에서는 문자열 연산이 많아지거나 멀티 쓰레드 환경, Race Condition 등의 상황에서 String만을 사용한다면 성능 문제를 일으킬 수 있습니다.
이번 글에서는 각 클래스의 특징을 이해하고, 상황에 맞는 적절한 클래스를 선택하는 방법을 정리해보겠습니다.
String 객체는 한번 값이 할당되면 그 공간은 변하지 않으며, 이것을 불변(Immutable)성 이라 합니다.
StringBuffer/StringBuilder 객체는 한번 값이 할당되더라도 한번 더 다른 값이 할당되면 할당된 공간이 변하며, 이것을 가변(mutable)성 이라 합니다.
간략하게 코드로 예를 들어보겠습니다.
String str = "String";
StringBuilder sb = new StringBuilder();
StringBuffer sbf = new StringBuffer();
sb.append("StringBuilder");
sbf.append("StringBuffer");
// 연산 전 객체들의 주소
System.out.println("String 객체의 주소 : "+str.hashCode());
System.out.println("StringBuilder 객체의 주소 : "+sb.hashCode());
System.out.println("StringBuffer 객체의 주소 : "+sbf.hashCode());
str += "Test";
sb.append("Test");
sbf.append("Test");
System.out.println("=============================");
// 연산 후 객체들의 주소
System.out.println("String 객체의 주소 : "+str.hashCode());
System.out.println("StringBuilder 객체의 주소 : "+sb.hashCode());
System.out.println("StringBuffer 객체의 주소 : "+sbf.hashCode());
해당 결과를 확인하면 아래와 같이 결과가 나오게 됩니다.
String 객체의 주소는 변경된 것이 확인되고, StringBuilder와 StringBuffer 객체의 주소는 변하지 않은 것을 확인할 수 있습니다.

두 클래스의 가장 큰 차이점은 동기화(Synchronization)의 유무입니다.
StringBuffer는 동기화 키워드를 지원하여 멀티쓰레드 환경에서 안전합니다.(thread-safe)
String 클래스도 불변성으로 인해 마찬가지로 멀티쓰레드 환경에서의 안정성을 가지고 있습니다.
StringBuilder는 동기화를 지원하지 않기 때문에 멀티쓰레드 환경에서 사용하는 것은 적합하지 않지만 동기화를 고려하지 않는 단일 쓰레드 환경에서는 StringBuffer 보다 뛰어납니다.
Thread-Safe란 멀티스레드 환경에서 여러 스레드가 동시에 특정 코드나 데이터에 접근하더라도 프로그램의 실행 결과가 올바르게 유지되는 것을 의미합니다.
Thread-Safe를 달성하기 위한 방법에는 다음과 같은 것들이 있습니다:
- 동기화: 여러 스레드가 공유 리소스에 동시에 접근하는 것을 제한하고, 한 번에 하나의 스레드만 해당 리소스를 사용할 수 있도록 합니다.
- 뮤텍스/세마포어: 운영체제 수준에서 제공되는 동기화 방법으로, 공유 리소스에 대한 동시 접근을 제어하는 데 사용됩니다.
- 원자적 연산과 Lock-Free 구조: 동기화 없이도 여러 스레드가 동시에 안전하게 작업을 수행할 수 있는 방법을 제공합니다.
오버헤드란 작업을 수행하는 데 필요한 추가적인 자원이나 시간을 의미합니다.
오버헤드는 일반적으로 성능 저하의 원인이 되며, 특정 작업이나 기능을 실행하는 데 필요한 부가적인 처리를 가리킵니다.
동기화 시 발생하는 오버헤드에는 다음과 같은 요소들이 포함됩니다
- 대기 시간: 동기화된 블록이나 메서드에 한 번에 하나의 스레드만 접근할 수 있기 때문에, 다른 스레드들은 대기 상태에 놓이게 되고 실행이 지연됩니다.
- 컨텍스트 스위칭: 스레드가 공유 리소스에 접근할 때마다 컨텍스트 스위칭이 발생할 수 있습니다.
컨텍스트 스위칭은 실행 중인 스레드나 프로세스의 상태를 저장하고, 대기 중인 다른 스레드를 불러오는 과정을 말합니다.
이 과정에서 추가적인 자원 사용과 시간 소모가 발생합니다.
이처럼 스레드 세이프와 동기화의 오버헤드를 이해하는 것은 성능 최적화와 안정적인 프로그램 개발에 중요합니다.
컴파일러에서 분석 할 때 최적화에 따라 다른 성능이 나올 수도 있지만, 일반적인 경우에는 아래 그림과 같이 사용하면 됩니다.
https://dev-jwblog.tistory.com/108#article-3--3--stringbuffer-/-stringbuilder