StringBuilder는 변경 가능한 문자열 시퀀스로 StringBuffer와 호환되는 API를 제공하지만 동기화를 보장하지 않는다. java doc에서 볼 수 있듯이 단일 스레드에서 StringBuffer의 대체제로 사용하도록 권장하고 있다. 멀티 스레드 환경에서는 StringBuilder가 아닌 StringBuffer를 사용해야 한다.
StringBuilder.java
/** * Constructs a string builder with no characters in it and an * initial capacity of 16 characters. */ @HotSpotIntrinsicCandidatepublicStringBuilder() { super(16); } /** * Constructs a string builder initialized to the contents of the * specified string. The initial capacity of the string builder is * {@code 16} plus the length of the string argument. * * @param str the initial contents of the buffer. */ @HotSpotIntrinsicCandidatepublicStringBuilder(String str) { super(str.length() +16);append(str); }
모든 StringBuilder에는 capacity(=16)가 존재하며 빌더에 포함된 문자열 길이가 capacity를 초과하지 않는 한 새로운 내부 버퍼를 할당할 필요가 없다.
AbstractStringBuilder.java
/** * Appends the specified string to this character sequence. * <p> * The characters of the {@code String} argument are appended, in * order, increasing the length of this sequence by the length of the * argument. If {@code str} is {@code null}, then the four * characters {@code "null"} are appended. * <p> * Let <i>n</i> be the length of this character sequence just prior to * execution of the {@code append} method. Then the character at * index <i>k</i> in the new character sequence is equal to the character * at index <i>k</i> in the old character sequence, if <i>k</i> is less * than <i>n</i>; otherwise, it is equal to the character at index * <i>k-n</i> in the argument {@code str}. * * @param str a string. * @return a reference to this object. */publicAbstractStringBuilderappend(String str) {if (str ==null) {returnappendNull(); }int len =str.length();ensureCapacityInternal(count + len); // capacity 체크putStringAt(count, str); count += len;returnthis; } /** * For positive values of {@code minimumCapacity}, this method * behaves like {@code ensureCapacity}, however it is never * synchronized. * If {@code minimumCapacity} is non positive due to numeric * overflow, this method throws {@code OutOfMemoryError}. */privatevoidensureCapacityInternal(int minimumCapacity) {// overflow-conscious codeint oldCapacity =value.length>> coder;if (minimumCapacity - oldCapacity >0) { value =Arrays.copyOf(value,newCapacity(minimumCapacity)<< coder); } } /** * The maximum size of array to allocate (unless necessary). * Some VMs reserve some header words in an array. * Attempts to allocate larger arrays may result in * OutOfMemoryError: Requested array size exceeds VM limit */privatestaticfinalint MAX_ARRAY_SIZE =Integer.MAX_VALUE-8; /** * Returns a capacity at least as large as the given minimum capacity. * Returns the current capacity increased by the same amount + 2 if * that suffices. * Will not return a capacity greater than * {@code (MAX_ARRAY_SIZE >> coder)} unless the given minimum capacity * is greater than that. * * @param minCapacity the desired minimum capacity * @throwsOutOfMemoryError if minCapacity is less than zero or * greater than (Integer.MAX_VALUE >> coder) */privateintnewCapacity(int minCapacity) {// overflow-conscious codeint oldCapacity =value.length>> coder;int newCapacity = (oldCapacity <<1) +2;if (newCapacity - minCapacity <0) { newCapacity = minCapacity; }int SAFE_BOUND = MAX_ARRAY_SIZE >> coder;return (newCapacity <=0|| SAFE_BOUND - newCapacity <0)?hugeCapacity(minCapacity): newCapacity; }privateinthugeCapacity(int minCapacity) {int SAFE_BOUND = MAX_ARRAY_SIZE >> coder;int UNSAFE_BOUND =Integer.MAX_VALUE>> coder;if (UNSAFE_BOUND - minCapacity <0) { // overflowthrownewOutOfMemoryError(); }return (minCapacity > SAFE_BOUND)? minCapacity : SAFE_BOUND; }
만약 append하면서 내부 버퍼가 오버플로우되면 자동으로 capacity가 증가한다 (ensureCapacityInternal).
StringBuffer
StringBuffer는 thread-safe한 변경 가능한 문자열 시퀀스이다. 내부 메소드는 필요한 경우에 동기화되므로 개별 스레드에서 수행한 메소드 호출 순서와 일치하는 일련의 순서로 작업이 진행된다.
jdk5 부터 StringBuffer 의 동일한 기능을 단일 스레드 환경에서 사용하도록 StringBuilder라는 클래스를 설계하였다. 따라서 단일 스레드 환경에서 동기화를 수행하지 않기에 더 빠른 StringBuilder를 우선적으로 사용해야 한다.
synchronized 매커니즘은 멀티스레드 환경에서 공유 자원에 대한 접근을 제어하기 위한 동시성 제어를 위한 것이다. 하지만 개발자가 synchronized 키워드를 직접 사용할 경우 실수의 여지가 있고, synchronized 메커니즘이 발전되어있지 않기 때문에 java5에서는 동시성 제어 기능이 포함된 Concurrency Utility Class(ex. ConcurrentHashMap)을 제공한다.
synchronzied 키워드의 특징은 다음과 같다.
synchronized라는 키워드가 붙은 code block은 한 순간에 오직 하나의 thread에게만 접근을 허락한다. 해당 block에 접근하려는 나머지 thread들은 block상태가 된다.
프로그램의 명령문이 재정렬되는 것을 방지한다. (JIT compiler는 결과에 차이가 없다면 코드의 순서를 변경할 수 있다. 하지만 synchronized 키워드가 붙어있다면, 코드 순서가 결과에 영향을 끼치므로 순서 변경을 방지해야 한다.)
synchronized 코드 블록 내부에 들어가기 전과 후에 thread lock 및 unlock을 보장한다.
문자열을 100,000,000번 append했을 때, StringBuilder가 시간 및 메모리 측면에서 StringBuffer보다 월등히 좋다. 당연한 결과이다. StringBuffer는 synchronized 키워드로 인해 동시성 제어를 위한 작업(코드 순서 보장, thread lock/unlock 보장)이 추가되어 싱글 스레드 환경일지라도 StringBuilder보다 성능이 안 좋다.