StringBuffer & StringBuilder
둘 다 가변 길이 문자열을 처리하는 데 사용되는 클래스이다.
StringBuffer
key : thread-safe
모든 메서드에 동기화가 적용되어 있어, thread-safe 로 멀티 스레드 환경에서 안전하다.
여러 스레드가 동시에 문자열을 수정해야 하는 경우 사용된다.
@Override
public synchronized StringBuffer append(Object obj) {
toStringCache = null;
super.append(String.valueOf(obj));
return this;
}
StringBuilder
key : thread-unsafe
내부적으로 동기화가 적용되어 있지 않기 때문에 멀티 스레드 환경에서 안전하지 않다.
단일 스레드 환경에서 문자열을 수정할 때 사용되며 성능이 중요하고 thread-safe가 필요하지 않은 경우 사용하는 것이 좋다.
@Override
public StringBuilder append(Object obj) {
return append(String.valueOf(obj));
}
StringBuilder 가 단일 스레드 환경에서 사용하기 적합한 이유
StringBuilder는 동기화 처리가 필요하지 않기 때문에 단일 스레드 환경에서 StringBuffer보다 빠르다.
StringBuffer는 모든 메서드에서 동기화 처리가 이루어지기 때문에, 메서드 호출 시마다 잠금(Lock) 획득 및 해제 과정이 발생하여 오버헤드로 인해 성능 저하가 발생할 수 있다.따라서 단일 스레드 환경에서는 동기화가 필요하지 않은 StringBuilder가 더 효율적이며, 메서드 호출 과정이 간단하고 경량화되어 StringBuffer보다 더 적은 자원을 소모한다.
멀티 스레드 환경에서는 StringBuffer가 안전하지만, 동기화가 불필요한 경우 StringBuilder가 더 빠르다.
불변성
객체가 한 번 생성된 후로 상태를 바꿀 수 없는 성질로 여러 스레드가 동시에 접근해도 안전하며, 추가적인 동기화가 필요하지 않다.
변경되지 않는 특성을 활용하여 캐싱 및 재사용이 가능하기 때문에 메모리 사용을 최적화할 수 있다.
String은 불변 객체로, 문자열을 수정할 때마다 새로운 객체가 생성된다. 반면, StringBuilder는 가변 객체로 내부 배열을 재사용하여 문자열을 수정하기 때문에, 새로운 객체가 생성되지 않아 메모리를 절약할 수 있다.
하지만 StringBuilder는 동기화 처리가 없기 때문에 멀티 스레드 환경에서는 주의가 필요하다.
StringBuilder를 멀티 스레드 환경에서 안전하게 사용하려면
StringBuilder는 동기화 처리가 되어 있지 않기 때문에 멀티 스레드 환경에서는 데이터의 일관성이 깨질 수 있다.
이를 해결하기 위해 synchronized, ReentrantLock와 같은 동기화 메커니즘을 사용할 수 있다.
간단한 경우에는 synchronized 블록으로 필요한 부분만 동기화하는 것이 적합하고, 복잡한 동기화가 필요한 경우에는 ReentrantLock을 사용하는 것이 좋다. 또한, 동기화가 기본적으로 처리된 StringBuffer를 사용하는 방법도 있다.
ReentrantLock과 Synchronized?
ReentrantLock과 Synchronized 둘 다 자바에서 동기화를 제공하지만 사용법과 기능 측면에서 차이가 있다.
synchronized 키워드는 블록이나 메서드 수준에서 동기화가 가능하며, 자동으로 락을 획득하고 해제한다. 사용이 간단하며 가독성이 좋다.
StringBuilder builder = new StringBuilder();
synchronized(builder) {
builder.append("hello");
}
ReentrantLock 클래스는 명시적으로 락을 획득하고 해제하는 작업이 필요하다. 타임아웃을 지정할 수 있으며 Condition 객체를 사용하여 보다 세밀한 스레드 제어가 가능하고 공정성을 설정할 수 있다. 복잡한 동기화 요구에 적합하지만 코드가 복잡해질 수 있다.
import java.util.concurrent.locks.Lock;
ReentrantLock lock = new ReentrantLock();
StringBuilder builder new StringBuilder();
lock.lock();
try{
builder.append("world");
} finally {
lock.unlock(); // lock 해제 작업 꼭 필요!!
}
ReentrantLock의 await 와 signal
ReentrantLock 클래스는 Condition 객체를 통해 스레드 간에 특정한 조건을 기다리고 신호를 보내는 기능을 제공한다.
await() 메서드는 현재 스레드를 일시적으로 멈추고 다른 스레드가 signal() 또는 signalAll() 을 호출할 때까지 기다린다.
singnal() 메서드는 하나의 대기 중인 스레드에게만 신호를 보낸다.
일반적으로 상태가 변경되어 하나의 스레만 깨어나야 할 때 사용된다.
공유 자원의 상태가 특정 조건을 만족할 때까지 대기하도록 스레드를 설정하는 경우 사용된다.
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockConditionExample {
private static final Lock lock = new ReentrantLock();
private static final Condition condition = lock.newCondition();
private static boolean isSignal = false;
public static void main(String[] args) throws InterruptedException {
Thread threadA = new Thread(() -> {
lock.lock();
try {
while (!isSignal) {
try {
condition.await(); // 대기
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Thread A!");
} finally {
lock.unlock();
}
});
threadA.start();
Thread.sleep(1000); // 1초 대기 후 signal을 보냄
lock.lock();
try {
isSignal = true;
condition.signal(); // signal을 보내어 스레드 A를 깨움
} finally {
lock.unlock();
}
threadA.join(); // 스레드 A가 종료될 때까지 대기
System.out.println("Thread A finished.");
}
}
StringBuffer의 동기화
StringBuffer는 내부적으로 동기화 처리가 되어있어 멀티 스레드 환경에서 안전하게 사용이 가능하다.
하지만 모든 메서드가 동기화 되어, 단일 스레드 환경에서 불필요하게 락을 걸고 해제하기 때문에 오버헤드가 발생할 수 있다. 복잡한 스레드 상호 작용 과정에서 데드락이 발생할 수 있으며, synchronized는 블록과 메서드 수준에서만 사용이 가능하기 때문에 세밀한 제어가 어려울 수 있다.
단일 스레드 환경이라면?
단일 스레드 환경에서 동기화가 동작하지 않더라도 synchronized 키워드가 있는 메서드를 호출하면 동기화에 필요한 추가적인 오버헤드가 발생한다.
synchronized 메서드는 항상 모니터 락을 획득하고 해제하는 과정을 거친다. 비록 단일 스레드 환경에서는 실제로 다른 스레드와의 경쟁이 없더라도, 자바는 이 메서드 호출 시에 락을 확인하는 작업을 한다. 이 과정이 추가적인 비용이 발생한다.
JVM 레벨에서 synchronized 키워드가 붙은 메서드나 블록은 동기화 관련 로직을 처리해야 하기 때문에, 단일 스레드라고 하더라도 이러한 추가적인 작업이 필요하다. 반면, StringBuilder는 이런 과정이 없기 때문에 더 빠르다.
'Java' 카테고리의 다른 글
@RequiredArgsConstructor, @AllArgsConstructor (0) | 2025.01.07 |
---|---|
index와 B-tree 그리고 Hash Index (0) | 2025.01.07 |
자바 직렬화 Serialization (0) | 2025.01.07 |
System.out.println()와 로그 (1) | 2025.01.07 |
synchroinzed와 DeadLock (0) | 2025.01.07 |