Java에서는 문자열을 다루기 위한 다양한 클래스가 제공되는데, 가장 대표적인 것이 String, StringBuilder, 그리고 StringBuffer입니다. 이 세 가지 클래스는 모두 문자열을 다루지만, 변경 가능성(Mutability)과 스레드 안전성(Thread Safety) 측면에서 차이가 있습니다.
String 클래스
String 클래스는 불변(Immutable) 객체로, 한 번 생성된 문자열은 수정할 수 없습니다. 문자열이 변경될 때마다 새로운 String 객체가 생성됩니다.
특징
- 불변성: String은 한 번 생성되면 내용을 변경할 수 없습니다.
- 새로운 객체 생성: 문자열을 조작할 때마다 새로운 객체가 생성되므로 문자열을 자주 변경하는 경우 메모리 효율이 떨어질 수 있습니다.
- 리터럴 풀 활용: String 객체는 String Pool에 저장되어, 동일한 리터럴 값이 있을 경우 같은 객체를 공유합니다.
String str = "Hello";
str = str + " World"; // 새로운 객체가 생성됨
StringBuilder 클래스
StringBuilder 클래스는 가변(Mutable) 객체로, 문자열을 직접 변경할 수 있습니다. 하지만 StringBuilder 클래스는 스레드 안전성을 제공하지 않기 때문에, 멀티 스레드 환경에서 문자열을 조작할 때 동시성 이슈가 발생할 수 있습니다.
특징
- 가변성: 문자열을 조작할 때 동일 객체 내에서 변경됩니다.
- 성능 우수: String에 비해 메모리 효율이 높고, 속도가 빠릅니다.
- 스레드 안전성 미제공: 여러 스레드에서 동시에 접근할 경우, 안전하지 않을 수 있습니다.
StringBuilder sb = new StringBuilder("Hello");
sb.append(" World"); // 같은 객체 내에서 문자열이 변경됨
System.out.println(sb.toString()); // "Hello World" 출력
StringBuffer 클래스
StringBuffer는 StringBuilder와 유사하게 가변 객체이지만, **스레드 안전(Thread-Safe)**합니다. 모든 메서드가 동기화(Synchronized) 되어 있어, 여러 스레드에서 접근해도 안전하게 사용할 수 있습니다. 다만, 이로 인해 StringBuilder보다 속도가 약간 느립니다.
특징
- 가변성: StringBuilder와 동일하게 같은 객체 내에서 문자열을 변경합니다.
- 스레드 안전성 제공: 메서드가 동기화되어 있어, 멀티스레드 환경에서도 안전하게 사용할 수 있습니다.
- 성능: 스레드 동기화 처리로 인해 StringBuilder보다 느리지만, String보다는 성능이 좋습니다.
StringBuffer sbf = new StringBuffer("Hello");
sbf.append(" World"); // 같은 객체 내에서 문자열이 변경됨
System.out.println(sbf.toString()); // "Hello World" 출력
멀티쓰레드 환경 StringBuilder, StringBuffer 테스트
StringBuilder와 StringBuffer를 각각 2개의 스레드를 생성하여 "A" 문자열을 100000번씩 append하는 테스트 코드입니다.
100000번씩 두 번 더했기 때문에 문자열 길이가 200000이 나오는 것을 예상했지만 StringBuilder는 멀티쓰레드 환경에서 안전성을 보장하지 않기 때문에 기대한 값이 나오지 않았고 StringBuffer는 기대한 값이 나와 테스트를 통과한 것을 확인할 수 있습니다.
또한 실행 시간을 측정한 결과 동기화 처리로 인해 StringBuffer 속도가 41ms로 StringBuilder보다 오래 걸린 것을 확인할 수 있습니다.
@SpringBootTest
public class TestClass {
@Test
public void stringBuilderTest() {
StringBuilder sharedBuilder = new StringBuilder();
Runnable task = () -> {
for (int i = 0; i < 100000; i++) {
sharedBuilder.append("A");
}
};
// 두 개의 스레드가 동일한 StringBuilder 인스턴스를 동시에 수정
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
long startTime = System.currentTimeMillis();
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
long endTime = System.currentTimeMillis();
// 실행 시간 계산 및 출력
long executionTime = endTime - startTime;
System.out.println("StringBuilder Thread execution time: " + executionTime + " ms"); // 4ms
Assertions.assertNotEquals(200000, sharedBuilder.length());
}
@Test
public void stringBufferTest() {
StringBuffer sharedBuffer = new StringBuffer();
Runnable task = () -> {
for (int i = 0; i < 100000; i++) {
sharedBuffer.append("A");
}
};
// 두 개의 스레드가 동일한 StringBuilder 인스턴스를 동시에 수정
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
long startTime = System.currentTimeMillis();
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
long endTime = System.currentTimeMillis();
// 실행 시간 계산 및 출력
long executionTime = endTime - startTime;
System.out.println("StringBuffer Thread execution time: " + executionTime + " ms"); // 41ms
Assertions.assertEquals(200000, sharedBuffer.length());
}
}
String, StringBuilder, StringBuffer 비교
구분 | String | StringBuilder | StringBuffer |
변경 가능성 | 불변 (Immutable) | 가변 (Mutable) | 가변 (Mutable) |
스레드 안전성 | 안전하지 않음 | 안전하지 않음 | 스레드 안전 (Synchronized) |
속도 | 가장 느림 | 가장 빠름 | StringBuilder보다 느림 |
사용 환경 | 변경이 필요 없는 문자열 | 단일 스레드에서 자주 변경하는 문자열 | 멀티스레드에서 자주 변경하는 문자열 |
'Java' 카테고리의 다른 글
[Java] Garbage Collection이란? (0) | 2024.11.11 |
---|---|
[Java] Java 예외 처리: 체크 예외와 언체크 예외, 예외와 에러의 차이 (2) | 2024.11.05 |
[Java] 스트림(Stream)이란 (0) | 2024.11.04 |
[Java] 제네릭이란? (0) | 2024.11.03 |
[Java] final 필드, 메소드, 클래스 (0) | 2024.10.30 |