
String,StringBuilder,StringBuffer는 모두 문자열을 처리하는데 사용되지만
내부적으로 작동하는 방식이 다르기 때문에 성능에 차이가 있다.
성능이 다른 주된 이유는 불변성(immutabilty)과 동기화(synchronization)와 관련이 있다.
String 객체는 한 번 생성되면 변경할 수 없는 불변성을 가진 불변 객체이다.
문자열을 수정하는 것 처럼 보이지는 작업(+= 연산 등)은 실제로 새로운 String 객체를 생성하고, 원래 문자열은 변경되지 않는다.
String은 불변 객체이기 때문에 새로운 문자열이 생성될 때마다 메모리에 새로운 객체가 만들어지므로, 문자열을 자주 변경하는 작업이 많은 경우 비효율적일 수 있다.
문자열을 자주 변경하는 경우 메모리 사용량이 증가하고, 객체 생성 및 메모리 관련 비용이 추가돼
이로 인한 성능이 저하될 수 있다.
StringBuilder는 문자열을 조작할 때 새로운 객체를 생성하지 않고,
기존 객체 내에서 문자열을 수정하는 가변성을 가진 가변 객체이다.
객체가 생성된 이후에도 문자열을 변경할 수 있으며,
내부적으로 버퍼(buffer)를 사용해 문자를 추가하거나 수정한다.
StringBuilder는 동기화가 되어 있지 않으므로 단일 스레드 환경에서 사용하는 것이 적합하다.
여러 스레드에서 동시에 접근하는 상황에서는 안전하지 않을 수 있다.
동기화가 필요 없기 때문에 오버헤드가 적고, 연속적인 문자열 연산에서 메모리와 성능면에서 유리하다.
따라서 문자열을 자주 수정해야할 경우 StringBuilder를 사용하는 것이 효율적이다.
StringBuffer도 StringBuilder와 마찬가지로 가변성을 가진 가변 객체로,
객체 생성 이후 문자열을 변경할 수 있다.
StringBuffer는 동기화(synchronized)를 지원해 멀티 스레드 환경에서 안전하게 사용할 수 있도록 메서드들이 동기화되어있다.
여러 스레드가 동시에 접근할 경우에도 데이터 무결성을 유지할 수 있다.
동기화 처리가 포함되어있어 StringBuilder보다 약간 느릴 수 있으나,
멀티 스레드 환경에서는 StringBuffer를 사용하는 것이 유리하다.
(무슨 의미?)
동기화가 된다는 것은 멀티 스레드 환경에서 여러 스레드가 동시에 접근할 때,
데이터의 일관성과 무결성을 보장하는 매커니즘을 의미한다.
여러 스레드가 동시에 하나의 객체나 자원에 접근하면 문제가 발생할 수 있기 때문에
동기화 처리가 필요할 때가 많다.
동기화된 메서드나 블록은 여러 스레드가 동시에 접근하지 못하게 막고,
한 스레드가 자원에 접근하는 동안 다른 스레드가 대기하도록 한다.
즉, 한 번에 한 스레드만 해당 자원에 접근할 수 있도록 제어하는 것이 동기화다.
동기화가 되면 상호 배제(mutual exclusion)가 적용되어 경쟁 상태(race condition)를 방지한다.
즉, 여러 스레드가 동시에 같은 데이터를 읽거나 수정할 때 발생할 수 있는 충돌을 막는다.
StringBuffer 클래스는 동기화가 지원되어 내부 메서드들이 synchronized 키워드로 보호되어 있다.
이를 통해 여러 스레드가 동시에 StringBuffer 객체에 접근해도 데이터의 무결성을 유지할 수 있다.
public synchronized StringBuffer append(String str) {
}
실제로 IntelliJ 에서 확인해보면 synchronized 키워드를 확인할 수 있다.

위와 같이 메서드에 synchronized 키워드가 붙어 있으면,
한 스레드가 이 메스드를 실행하는 동안 다른 스레드는 해당 메서드가 접근할 수 없다.
✨ [참고] toString() 메서드
StringBuilder, StringBuffer와 함께 자주 사용되는 toString() 메서드는
저장된 문자열 데이터를 새로운 String 객체로 변환하여 반환한다.
내부적으로 가변적인 문자열을 다루지만 toString()을 호출하면,
그 순간 해당 내용을 불변 객체인String으로 반환한다.