티스토리 뷰
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 사용
'기술 공부 > Tech Interview' 카테고리의 다른 글
[Java] 자바 컴파일 과정 / JVM / 동적 로딩 (0) | 2024.10.21 |
---|---|
[Java] Object 클래스 + 스레드(Thread 간단 개념) (2) | 2024.10.04 |
댓글