String
String은 불변(immutable)객체로, 한 번 생성된 문자열은 수정할 수 없다. 문자열을 수정하게 되면 기존 문자열을 수정하는 것이 아니라 새로운 문자열 객체를 생성한다. 따라서 문자열을 많이 수정하면 할 수록 공간의 낭비와 속도 저하가 발생하게 된다.
하지만 이러한 불변성 덕에 String Constant Pool에 각 리터럴 문자열을 하나만 저장 후 다시 사용하여 힙 공간을 절약할 수있으며 멀티스레드 환경에서 자유롭게 공유할 수 있게 된다.
StringBuffer
StringBuffer sb = new StringBuffer(); // 기본 크기 16
StringBuffer sb = new StringBuffer(50); // 초기 버퍼 크기를 50으로 설정
StringBuffer sb = new StringBuffer("Hello"); // 버퍼크기는 16 + 초기 문자열의 길이로 설정됨
StringBuffer는 문자열을 수정하면 새로운 객체를 생성하지 않고 기존 객체를 변경하는데, 기존에 할당된 버퍼 내에서 작업을 한다. 따라서 문자열 조작이 빈번할 경우 String보다 더 효율적이다.
StringBuffer에서 버퍼(buffer)는 문자열 데이터를 저장하고 조작하기 위해 미리 확보된 메모리 공간을 의미한다. 초기에는 기본 크기(16)의 버퍼가 할당되지만, 버퍼 크기를 초과하는 문자열이 추가되면 자동으로 버퍼 크기를 늘린다. 이 과정에서 새로운 메모리 공간을 할당하고 기존 데이터를 복사한다.
버퍼가 확장될 때는 새로운 메모리 공간을 할당하고 데이터를 복사해야하므로 비용이 발생할 수 있다. 따라서 성능을 최적화하려면 초기 버퍼 크기를 예상하여 설정하는 것이 좋다.
@Override
@HotSpotIntrinsicCandidate
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
StringBuffer는 동기화(synchronized) 되어 있어, 멀티 스레드 환경에서도 안전(thread-safe)하게 동작한다.
synchronized는 한 번에 하나의 스레드만 이 메서드를 실행할 수 있도록 보장한다. 즉, 공유된 StringBuffer객체에 여러 스레드가 동시에 접근하더라도 한 스레드가 메서드를 실행하는 동안 다른 스레드는 대기한다. 따라서 데이터가 손상되거나 예외가 발생하지 않는다.
StringBuffer Example
public class StringBufferConcurrent {
// 모든 스레드가 접근 가능한 StringBuffer 객체를 생성
static final StringBuffer sb = new StringBuffer();
public static void main(String[] args) throws Exception {
// 300개의 WriterThread 스레드 생성
int NUM_WRITERS = 300;
ArrayList<WriterThread> threads = new ArrayList<WriterThread>(NUM_WRITERS);
// 각 스레드는 WriterThread0, WriterThread1 ... 등의 이름을 가짐
for (int i = 0; i < NUM_WRITERS; i++) {
WriterThread wt = new WriterThread("writerThread" + i);
threads.add(wt);
wt.start();
}
// 메인 스레드가 각 WriterThread가 종료될 때까지 대기
for (int i = 0; i < threads.size(); i++) {
threads.get(i).join();
}
// 결과를 출력
System.out.println(sb);
}
public static class WriterThread extends Thread {
public WriterThread(String name) {
super(name);
}
public void run() {
String nameNl = this.getName() + "\n";
for (int i = 1; i < 20; i++) {
sb.append(nameNl);
}
}
};
}
해당 코드를 돌려보면 긱 WriterThread가 20번씩 출력되는 것을 볼 수 있다. 하지만 숫자가 순차적으로 출력되지 않고 번갈아 나오는데, 이는 멀티스레드의 동시성(Concurrency)때문이다. 자바에서 여러 스레드가 동시에 실행되면 스레드의 실행 순서는 JVM의 스케쥴러에 의존하기 때문이다. synchronized 키워드는 데이터 무결성을 보장하기 위한 것이지, 스레드 실행 순서를 보장하진 않는다.
StringBuilder
StringBuilder도 가변 객체로 StringBuffer와 유사하게 동작하지만 동기화를 지원하지 않는다. 따라서 단일 스레드 환경에서 동기화가 불필요하기 때문에 StringBuffer보다 더 빠른 성능을 제공한다.
참고 자료
'Java' 카테고리의 다른 글
JDK와 JRE의 차이 (0) | 2024.11.20 |
---|---|
접근제어자 캡슐화, 정보 은닉 (0) | 2024.11.20 |
String 문자열 String Pool, ==과 equals() 차이점 (0) | 2024.11.15 |
기본 타입과 참조 타입 (0) | 2024.11.15 |
JVM의 동작방식과 구조 (0) | 2024.11.14 |
댓글