티스토리 뷰

  String StringBuffer StringBuilder
변경 Immutable
(불변)
Mutable
(가)
Mutable
동기화   Synchronized 가능 Synchronized 불가능

 

🐬 String 클래스 특징

  • new 연산자로 생성된 객체(인스턴스)의 메모리 공간은 변하지 않음(Immutable)

  • 생성된 문자열 수정/연산 시 불변성을 가지기 때문에 때마다 새 String 객체가 생성됨

String str = "Hello";
str = str + " World!";
  • 위 코드는 "Hello"와 "World!" 연결 과정에서 새 String 객체가 생성,
    str 객체는 이 새 객체를 참조하게 됨 (Overhead 발생)
    원래의 "Hello" 객체는 여전히 메모리에 존재하지만 더 이상 str 변수와 연결되지 않음

  • 객체가 불변하므로 String 객체는 Multithread에서 동기화를 신경 쓸 필요가 없다.
    ⇢ 스레드 안전(Thread-Safe)하며, 조회 연산에 매우 큰 장점이 되겠다.

  • 문자열 수정이 잦은 경우, StringBuffer / StringBuilder 사용이 효율적일 수 있음

  • Java에서는 메모리 관리가 자동으로 이루어지며, 사용하지 않는 객체는 Garbage Collector가 처리
String str1 = "Hello";
String str2 = "Hello";
// Hello 문자열 리터럴이 이미 문자열 풀에 존재하기 때문에
// 새로 String 객체를 생성하지 않고 기존 "Hello" 객체를 참조
// str1와 str2가 메모리에서 동일한 객체를 가리킴

String str3 = new String("Hello");
// Str3은 문자열 풀에 있는 "Hello" 와는 다른 새로운 객체를 참조
  • 문자열 리터럴은 String 객체 생성 시 사용, 동일한 리터럴은 JVM 에서 동일한 객체 참조(객체 재사용)

  • 문자열 풀(String pool) : 문자열 리터럴을 저장하는 특별한 메모리 공간
    문자열 리터럴이 생성될 때, JVM이 해당 풀에서 같은 문자열이 이미 존재하는지 확인
    ⇢ Java에서는 같은 문자열 리터럴이 여러 번 쓰이면, 메모리 절약을 위해 동일한 String 객체를 공유.

 

⇨ String 클래스는 문자열 연산이 적고, 조회가 많은 멀티 스레드 환경에 적합

 

 

🐬 StringBuffer와 StringBuilder 클래스 특징

  • 공통점

    - new 연산으로 클래스를 한 번만 만듦 (Mutable)
    - 문자열 연산 시 새로 객체를 만들지 않고, 크기가 변경됨
    - StringBuffer와 StringBuilder 클래스 내 메서드 동일

  • 차이점

    - StringBuffer는 Thread-Safe / StringBuilder는 Thread-safe X
    - StringBuffer는 동기화로 인해 성능이 다소 떨어질 수 있으며, 멀티스레드 환경에서 안전성 보장을 위해
    잠금(lock)을 사용하므로, 오버헤드가 발생(단순히 새로운 객체가 생성되는 것 이상으로 동기화로 인한 잠금 비용,
    스레드 간 대기 시간, 메모리 관리 등의 여러 요소로 인한 성능 저하가 발생할 수 있다는 것을 의미)
    - StringBuilder는 동기화가 없기 때문에 싱글스레드 환경에서 더 높은 성능이 제공

 

  ↓

 

StringBuffer : 문자열 연산이 많은 Multi-Thread 환경에 적합

  • 스레드 안전성 보장 : StringBuffer는 메서드가 synchronized로 선언되어 여러 스레드 동시 접근에 안전
    하나의 스레드가 작업을 수행하는 도중 나머지 스레드는 대기데이터 일관성 유지 가능
  • Multi-Thread 환경에서는 여러 개의 스레드가 같은 자원에 접근하거나 수정할 수 있기 때문에라도
    스레드의 안정성은 매우 중요한 요소, 따라서 StringBuffer 클래스는 멀티 스레드 환경에 적합한 것.

 

StringBuilder : 문자열 연산이 많은 Single-Thread / Thread 신경 안 쓰는 환경에 적합

  • 성능 : StringBuilder는 메서드가 synchronized로 선언되지 않아, 메서드 호출 시 잠금(락)처리 안 함
    그러므로 단일 스레드에서 성능이 더 빠름 → 메서드 호출 시 잠금 피함, 문자열 조작 시 더 효율적 수행
  • 애플리케이션이 단일 스레드(Single-Thread)로 실행되거나 여러 스레드가 자원에 동시 접근하지 않는 경우,
    StringBuilder를 사용하는 것이 성능을 최적화하는 데 도움이 된다.

 

 

🐬 잠금(Monitor Lock)과 동기화(Synchronized)

잠금 : 특정 자원에 대한 고유 락을 획득해 스레드의 접근을 제어하는 것
동기화 : 멀티 스레드에서 데이터 일관성 유지를 위해 스레드 간 접근을 조정하는 것

1. 고유 락(Monitor Lock)

  • synchronized 키워드가 붙은 메서드/블록은 특정 객체에 대한 고유 락을 획득
  • 락을 획득한 스레드만 해당 메서드/블록을 실행할 수 있다.
  • 다른 스레드는 이 락을 획득할 수 있을 때까지 대기하게 된다.

2. 동기화(Synchronized)

  • 여러 스레드가 동시에 같은 자원(ex: StringBuffer 객체)에 접근할 때 데이터 일관성 유지를 위해 동기화 필요
  • 동기화를 통해서 하나의 스레드가 작업을 완료하기 전까지는 다른 스레드가 해당 자원에 접근치 못하게 함

3. StringBuffer의 경우

public synchronized void append(String str) {
    // 문자열을 추가하는 코드
}
  • StringBuffer의 메서드는 기본적으로 synchronized로 구현되어 있어, 멀티 스레드 환경에서 안전함(Thread-safe)
  • 한 스레드가 위와 같은 메서드를 호출 시, 다른 스레드는 이 메서드가 완료될 때까지 기다린다.
public class StringBufferExample {
    public static void main(String[] args) {
        StringBuffer sb = new StringBuffer("Hello");

        // 여러 스레드에서 StringBuffer 객체에 접근
        Thread thread1 = new Thread(() -> {
            sb.append(" World!");
            System.out.println("Thread 1: " + sb);
        });

        Thread thread2 = new Thread(() -> {
            sb.append(" Everyone!");
            System.out.println("Thread 2: " + sb);
        });

        thread1.start();
        thread2.start();
    }
}
  • StringBuffer의 append 메서드는 synchronized로 구현되어 있다.
  • 두 스레드가 동시에 접근해도 데이터 일관성 유지, 한 스레드 작업 수행 시 다른 스레드는 대기
  • 실행 결과
    - Thread 1 이 먼저 실행
    Thread 1 : Hello World!
    Thread 2 : Hello World! Everyone!
    - Thread 2 가 먼저 실행
    Thread 2 : Hello Everyone!
    Thread 1 : Hello Everyone! World!
    - 두 스레드가 거의 동시에 실행
    Thread 1 : Hello World!
    Thread 2 : Hello World! Everyone!

4. StringBuilder의 경우

public class StringBuilderExample {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder("Hello");

        // 여러 스레드에서 StringBuilder 객체에 접근
        Thread thread1 = new Thread(() -> {
            sb.append(" World!");
            System.out.println("Thread 1: " + sb);
        });

        Thread thread2 = new Thread(() -> {
            sb.append(" Everyone!");
            System.out.println("Thread 2: " + sb);
        });

        thread1.start();
        thread2.start();
    }
}
  • StringBuilder의 append 메서드는 synchronized가 적용되지 않으므로 데이터 일관성 보장 안 됨
  • 두 스레드가 같은 객체에 접근할 때(위 코드에서 sb), 한 스레드의 작업이 완료되기 전에
    다른 스레드가 접근/수정할 수 있기 때문에 결과를 예측할 수 없게 된다. (Thread-safe 하지 않음)

 

결론
StringBuffer : 메서드 synchronized 구현 / 멀티 스레드 환경에서 안전 (Thread-safe)
StringBuilder : 메서드 synchronized 구현 안 됨 / 멀티 스레드 환경에서 데이터 일관성 보장 안 됨

또한 위 두 클래스 간의 공통점으로 꼽았던 '동일한 메서드 사용'은
동일한 메서드 이름을 사용하지만 실질적으로 동기화의 유무 차이가 있으며 사용 환경이 달라진다.
일반적으로 멀티스레드 환경 : StringBuffer / 단일스레드 환경 : StringBuilder 사용

 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG more
«   2025/06   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
글 보관함