[JAVA] String, StringBuffer, StringBuilder의 차이
String, StringBuffer, StringBuilder의 차이를 정리하면 아래와 같다.
String은 불변객체이고 StringBuffer, StringBuilder는 가변객체이다.
StringBuffer는 멀티스레드 환경에서 사용되고 StringBuilder는 싱글스레드 환경에서 사용된다.
왜 이런 차이를 보이는 것일까?
String이 불변객체인 이유
String이 불변객체인 이유는 JAVA가 문자열 리터럴을 String Constant Pool로 관리하기 때문이다.
"안녕하세요", "반갑습니다.", "hello" 처럼 선언된 문자열은 문자열 리터럴로 간주되어 Heap 메모리 안에 생성된 String Constant Pool에 문자열 객체로 등록된다.
스레드A가 "안녕하세요" 문자열 객체를 리터럴로 선언하여 String Constant Pool에 등록했다. 이어서 스레드B도 "안녕하세요" 문자열 객체를 리터럴로 선언하여 String Constant Pool에 등록하려고 한다. 이때 만약 이미 동일한 문자열 리터럴이 존재한다면 기존 문자열 객체의 주소를 참조변수에 저장한다.
그러므로 스레드A의 참조변수 a와 스레드B의 참조변수b는 동일한 문자열 객체를 바라보는 것이다.
그런데 만약 스레드A가 "안녕하세요" 문자열 객체에 접근하여 "안녕"으로 바꾼다고 가정해보자. 스레드B는 "안녕하세요"로 알고 있지만 스레드A에 의해 강제로 "안녕"으로 바뀌어 버리게 되는 것이다. 이처럼 JAVA는 멀테스레드 환경에서 문자열 리터럴을 String Constant Pool 구조로 관리하기에, 데이터 정합성이 위협받는다. 이를 위해, JAVA는 문자열 객체에 저장된 문자열 데이터가 절대 변경되지 않도록 final 키워드로 막아놓았다.
바이트 배열에 문자열 데이터가 저장되는데, final 키워드로 선언되어 있어 한번 초기화되면 변경이 불가능해진다. 이로써 한번 생성된 문자열 객체는 다른 스레드에 의해 변경되지 않으니, Thread Safe하다고 할 수 있다.
String이 불변이어서 생기는 문제
문자열 객체는 여러 메소드를 가진다.
concat, trim, toUpperClass 등의 메소드로 문자열을 조작하는 것처럼 보이지만, 사실 새로운 문자열 객체를 생성하는 것이다. 그러다보니 불변객체인 String 문자열을 조작하면 할수록 Heap에는 새로 생성된 객체가 생성되어, GC가 자주 발생하여 프로그램 성능이 떨어지게 된다.
그러므로 문자열에 문자를 추가하거나 변경해도 새로운 문자열이 생성되지 않은 가변객체가 필요한데,
그것이 StringBuffer와 StringBuilder이다.
StringBuffer, StringBuilder
StringBuffer와 StringBuilder는 둘다 AbstractStringBuilder 클래스를 상속한다.
AbstractStringBuilder는 문자열이 저장되는 바이트 배열에 final 키워드가 없다. 즉, 가변객체라는 의미이다.
앞서 말했듯, String이 불변객체이어야 하는 이유는 멀티스레드 환경에서 문자열 데이터의 정합성을 보장하기 위함이다. 그러므로 가변객체로 풀어주는 대신, 정합성을 보장할 무언가가 필요하다. 그것이 바로 sychronized 키워드이다.
StringBuffer는 문자열 데이터를 조작하는 모든 메소드에 sychronized 키워드가 선언되어 있다. sychronized 키워드가 선언된 객체는 임계영역을 만들어 상호배제(Mutex)를 구현하다. 다시말하여, 문자열 데이터에 접근하려는 스레드는 '순서'를 지켜야 한다. 이로써 멀티 스레드 환경에서 Thread Safe하게 문자열을 조작할 수 있다.
StringBuilder는 JDK 1.5 이후에 등장하였다.
StringBuilder가 등장한 이유는 StringBuffer가 싱글스레드 환경에서 동기화 오버헤드가 발생하기 때문이다. 멀티 스레드 환경이라면 StringBuffer의 동기화 기능이 반드시 필요하지만 싱글스레드 환경에서는 필요없다. 경쟁이 없는 싱글스레드 환경에서는 StringBuilder를 사용하면 된다. StringBuilder는 synchronized 키워드가 선어되어 있지않아 상호배제가 이루어지지 않는다. 그러므로 스레드가 하나라면 StringBuilder를 사용해야 더 좋은 성능을 낼 수 있다.