티스토리 뷰

JAVA

StringBuilder 와 StringBuffer 비교

소농배 2022. 3. 23. 15:06

StringBuilder 와 StringBuffer 모두 AbstractStringBuilder 를 상속받아 메서드를 구현하고 있다.

두 클래스는 모두 String 을 만들기 위해 사용되지만 어떤 차이점으로 인해 구분되어있는지를 확인해본다.

 

 

append() 함수로 알아보는 두 클래스의 차이

StringBuilder.java 의 append() 함수

    @Override
    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }

 

StringBuffer.java 의 append() 함수

    @Override
    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }

 

StringBuilder 의 append() 와 StringBuffer 의 append() 모두 내부적으로 부모 클래스인 AbstractStringBuilder 의 append() 함수를 호출하는것을 알 수 있다. 하지만 두가지 다른점이 존재한다.

  1. synchronized
  2. toStringCache

StringBuffer 에는 StringBuilder 와는 다르게 append() 함수에 synchronized 가 달려있고 부모 클래스의 append() 함수를 호출하기 전에 toStringCache 를 null 로 초기화 하고 있다.

 

StringBuffer 에는 synchronzied 가 붙어 있는 이유

StringBuffer 의 append() 함수에 synchronized 가 붙어있다는 이유는 동기화를 위해 붙어있다는 것을 알 수 있다. 즉, 부모 클래스인 AbstractStringBuilder 의 append() 함수는 Thread Safe 하지 않다고 생각할 수 있다. 

그렇다면 StringBuffer 는 synchronized 로 인해 Thread Safe 하고 StringBuilder 는 Thread Safe 하지 않으니 이 것을 테스트 코드로 확인해본다.

 

Main Thread 를 제외한 2개의 Thread 에서 각각 StringBuffer 와 StringBuilder 에 문자열 한가지를 10000번씩 append()한다.

public class Main {
	public static void main(String[] args) throws InterruptedException {
		StringBuffer stringBuffer = new StringBuffer();
		StringBuilder stringBuilder = new StringBuilder();

		Thread thread1 = new Thread(() -> {
			for (int i = 0; i < 10000; i++) {
				stringBuffer.append("A");
				stringBuilder.append("A");
			}
		});
		thread1.start();

		Thread thread2 = new Thread(() -> {
			for (int i = 0; i < 10000; i++) {
				stringBuffer.append("B");
				stringBuilder.append("B");
			}
		});
		thread2.start();

		thread1.join();
		thread2.join();

		System.out.println("StringBuffer.length: "+ stringBuffer.length());
		System.out.println("StringBuilder.length: "+ stringBuilder.length());
	}
}

결과

 

StringBuffer 는 누락 없이 모든 문자열이 append 되어 20000 개의 문자를 포함하였지만 StringBuilder 는 일부 문자열이 누락되어 5개가 부족한 19995 개의 문자를 갖게 되었다.

이를 통해 StringBuffer 는 Multi Thread 환경에서 Thread Safe 하지만 StringBuilder 는 Thread Safe 하지 않음을 확인할 수 있었다.

 

그렇다면 부모 클래스의 어떤 코드로 인해서 Thread Safe 하지 않은지를 확인해보자

    public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }

AbstarctStringBuilder 의  thread safe 하지 않은 의심스러운 메서드는 아래 두가지이다.

  • ensureCapacityInternal() : AbstractStringBuilder 맴버 변수인 value 의 char[] 길이를 늘리는 역할을 하는 함수
  • str.getChars() : 전달받은 char[] 로 해당 String 값을 복사

ensureCapacityInternal()

    private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        if (minimumCapacity - value.length > 0) {
            value = Arrays.copyOf(value,
                    newCapacity(minimumCapacity));
        }
    }

String.getChars()

    public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
        if (srcBegin < 0) {
            throw new StringIndexOutOfBoundsException(srcBegin);
        }
        if (srcEnd > value.length) {
            throw new StringIndexOutOfBoundsException(srcEnd);
        }
        if (srcBegin > srcEnd) {
            throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
        }
        System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
    }

 

StringBuffer 에서 append() 가 누락되는 시나리오

사나리오1 

 Thread1 - String.getChars() 를 통해 "A" 를 value 에 복사하는 중

 Thread2 - ensureCapacityInternal() 를 통해 부족한 배열 길이를 늘리기 위해 value 복사 중

 Thread1 - "A" 를 value 에 추가 완료

 Thread2 - value 배열 길이 늘리기 위한 복사 완료

 

Thread1 이 "A" 를 value 에 추가했지만 Thread2 가 길이를 늘리기 위해 복사한 value 는 Thread1 이 "A" 를 추가하기 이전의 value 이다. 이렇게 되면 Thread1 이 추가한 "A" 가 유실된다

 

시나리오2

 Thread1 - String.getChars() 를 통해 "A" 를 value 에 복사하는 중

 Thread2 - String.getChars() 를 통해 "B" 를 value 에 복사하는 중

 Thread1 - "A" 를 추가한 value 복사 완료

 

 Thread2 - "B" 를 추가한 value 복사 와료

 

Thread1 이 "A" 를 value 에 추가했지만 Thread2 가 복사를 완료한 시점의 value 는 Thread1 이 "A" 를 추가하기 이전의 value 이다. 이렇게 되면 Thread1 이 추가한 "A" 가 유실된다.

 

 

toStringCache 의 역할

StringBuffer 는 맴버 변수로 toStringCache 를 갖는다. 이는 AbstractStringBuilder 와 StringBuilder 에는 없는 맴버 변수이다.

이 변수의 역할은 toString() 함수가 호출될때 value 배열의 값을 복사한 채로 가지고 있다가 또 다시 toString() 이 호출되었을때 value 배열에 변경이 없다면 toStringCache 를 사용하여 String 을 만들게 된다.

 

value 배열을 그대로 사용해서 String 을 만들지 않고 value 의 복사본인 toStringCache 를 사용하는 이유가 무엇일까?

 

 

 

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함