[Java] String, Stringbuffer, Stringbuilder 차이

Jeong Yeonwoo·2024년 7월 29일

자바

목록 보기
2/2

Java에서 문자열을 다루는 대표적인 클래스로는 String, StringBuffer, StringBuilder가 있습니다. 작은 시스템에서는 대부분 String을 사용해도 큰 문제가 발생하지 않지만, 대규모 서비스와 시스템에서는 문자열 연산이 많아지거나 멀티 쓰레드 환경, Race Condition 등의 상황에서 String만을 사용한다면 성능 문제를 일으킬 수 있습니다.

이번 글에서는 각 클래스의 특징을 이해하고, 상황에 맞는 적절한 클래스를 선택하는 방법을 정리해보겠습니다.

1. String vs StringBuffer/StringBuilder

  • 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 객체의 주소는 변하지 않은 것을 확인할 수 있습니다.

2. String

  • String 클래스는 불변(Immutalbe)하기 때문에, 문자열을 연산하는 시점에 새로운 String 인스턴스가 메모리에 할당이 됩니다.
    그러므로 변하지 않는 문자열을 자주 읽어들일 때 사용하면 좋은 성능을 기대 할 수 있습니다.
  • 그러나 문자열 추가,수정,삭제 등의 연산이 자주 일어나는 로직에서는 String 클래스를 사용하면 힙(Heap) 메모리에 많은 Garbage(가비지)가 생성되어 힙메모리 부족으로 어플리케이션 성능에 큰 영향이 있을 수 있습니다.

3. StringBuffer vs StringBuilder

  • 두 클래스의 가장 큰 차이점은 동기화(Synchronization)의 유무입니다.

  • StringBuffer는 동기화 키워드를 지원하여 멀티쓰레드 환경에서 안전합니다.(thread-safe)

  • String 클래스도 불변성으로 인해 마찬가지로 멀티쓰레드 환경에서의 안정성을 가지고 있습니다.

  • StringBuilder는 동기화를 지원하지 않기 때문에 멀티쓰레드 환경에서 사용하는 것은 적합하지 않지만 동기화를 고려하지 않는 단일 쓰레드 환경에서는 StringBuffer 보다 뛰어납니다.

📝 참고

Thread-Safe

Thread-Safe란 멀티스레드 환경에서 여러 스레드가 동시에 특정 코드나 데이터에 접근하더라도 프로그램의 실행 결과가 올바르게 유지되는 것을 의미합니다.
Thread-Safe를 달성하기 위한 방법에는 다음과 같은 것들이 있습니다:

  • 동기화: 여러 스레드가 공유 리소스에 동시에 접근하는 것을 제한하고, 한 번에 하나의 스레드만 해당 리소스를 사용할 수 있도록 합니다.
  • 뮤텍스/세마포어: 운영체제 수준에서 제공되는 동기화 방법으로, 공유 리소스에 대한 동시 접근을 제어하는 데 사용됩니다.
  • 원자적 연산과 Lock-Free 구조: 동기화 없이도 여러 스레드가 동시에 안전하게 작업을 수행할 수 있는 방법을 제공합니다.

오버헤드

오버헤드란 작업을 수행하는 데 필요한 추가적인 자원이나 시간을 의미합니다.
오버헤드는 일반적으로 성능 저하의 원인이 되며, 특정 작업이나 기능을 실행하는 데 필요한 부가적인 처리를 가리킵니다.
동기화 시 발생하는 오버헤드에는 다음과 같은 요소들이 포함됩니다

  • 대기 시간: 동기화된 블록이나 메서드에 한 번에 하나의 스레드만 접근할 수 있기 때문에, 다른 스레드들은 대기 상태에 놓이게 되고 실행이 지연됩니다.
  • 컨텍스트 스위칭: 스레드가 공유 리소스에 접근할 때마다 컨텍스트 스위칭이 발생할 수 있습니다.
    컨텍스트 스위칭은 실행 중인 스레드나 프로세스의 상태를 저장하고, 대기 중인 다른 스레드를 불러오는 과정을 말합니다.
    이 과정에서 추가적인 자원 사용과 시간 소모가 발생합니다.
    이처럼 스레드 세이프와 동기화의 오버헤드를 이해하는 것은 성능 최적화와 안정적인 프로그램 개발에 중요합니다.

4. 정리

컴파일러에서 분석 할 때 최적화에 따라 다른 성능이 나올 수도 있지만, 일반적인 경우에는 아래 그림과 같이 사용하면 됩니다.

  • String : 문자열 연산이 적고, 멀티쓰레드 환경일 경우
  • StringBuffer : 문자열 연산이 많고, 멀티쓰레드 환경일 경우
  • StringBuilder : 문자열 연산이 많고, 단일쓰레드이거나 동기화를 고려하지 않아도 되는 경우

참고사이트

https://dev-jwblog.tistory.com/108#article-3--3--stringbuffer-/-stringbuilder

0개의 댓글