1 minute read

is final? is thread safe?

  • String 과 StringBuilder, Buffer 의 차이점을 아시나요?
  • 실제로 기술 면접에서 한 번 경험한 질문입니다. 이 질문의 포인트는 결국 두 가지라고 생각합니다.
  • String 클래스는 불변, StringBuilder, Buffer 는 가변이다.
  • String, StringBuilder 는 쓰레드 세입하지 않고 StringBuffer 는 쓰레드 세입하다.

String

@Stable
private final byte[] value;
  • String의 큰 특징은 위의 코드에서 보이듯이 불변이라는 것입니다. final
  • String 은 두 가지 방법으로 생성할 수 있습니다.
  • String str1 = new String(”abc”)
    • 힙 메모리에 인스턴스 생성
    • 이런 방식으로 생성한다면 intelliJ 에서는 불필요하다는 것을 알려줍니다. redundant
  • String str2 = “abc”
    • 상수 풀 사용
    • 상수 풀의 문자열은 모두 같은 주소를 가집니다.
    • 상수 풀을 사용함으로써 메모리를 효율적으로 사용할 수 있습니다.
  • String 을 수정하면 기존의 String 객체가 수정되는 것이 아닌 새로운 String 객체가 생성됩니다.
    • String 객체는 불변이기 때문입니다.
    • 새로운 String 문자열 객체를 생성 후 상수풀에 추가한다!
    • 빈번하게 문자열이 바뀌는 로직이라면?
      • 기존 String 객체가 남아있으므로 메모리 낭비 가 발생할 수 있다.

StringBuilder, StringBuffer

abstract class AbstractStringBuilder
byte[] value; //final이 아니다.
  • StringBuilder 와 StringBuffer 는 가변객체입니다.
  • 위의 코드에서 보이듯이 final 키워드가 없습니다.
    • 가변적인 배열을 멤버 변수를 가집니다.
    • 이를 통해 새로운 인스턴스를 생성하지 않고 문자열을 변경합니다.
  • 문자열의 빈번한 수정이 있는 로직이라면 유용하며 메모리 낭비를 줄입니다.

StringBuffer

  • 내부적으로 락을 거는 매커니즘이 있으며, 이를 통해 멀티쓰레드 프로그래밍에서 동기화(Synchronization) 을 보장합니다.
    • 즉, 쓰레드 세입합니다.
    • StringBuilder 는 쓰레드 세입하지 않습니다.
  • 그렇다면, StringBuffer가 제일 좋은데?
    • CASE BY CASE! 락을 거는 메커니즘 때문에 무거우며, 오버헤드 발생 가능성이 있습니다.
    • 단일 쓰레드 프로그램에서는 StringBuilder 사용이 권장됩니다.
    • 멀티 쓰레드 프로그램에서 유용합니다.

TestCode

import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

public class StringClassTest {

    @Test
    void newString() {
        String java = new String("java");
        String java1 = new String("java");

        assertThat(java).isNotSameAs(java1);

        String java2 = "java";
        String java3 = "java";

        assertThat(java2).isSameAs(java3);
    }

    @Test
    void modifyString() {
        // immutable String
        String java = "java";
        int hashCode = System.identityHashCode(java);

        String spring = "spring";
        java = java.concat(spring);
        // 새 객체가 생성된다. 기존 hashcode 의 String 객체는 GC의 대상이 된다.(하지만 상수풀에 들어간 문자열은 보통 프로그램 전반에서 사용되어 프로그램 종료시 GC의 대상이 되는 경우가 많다.)
        assertThat(System.identityHashCode(java)).isNotEqualTo(hashCode);
    }

    @Test
    void stringBuilder() {
        // mutable
        StringBuilder sb = new StringBuilder();
        int sbHashCode = System.identityHashCode(sb);

        sb.append("java");
        sb.append("spring");

        // 기존 StringBuilder 에 가변적으로 추가된다. 새 객체 생성 X
        assertThat(System.identityHashCode(sb)).isEqualTo(sbHashCode);
    }

    @Test
    void stringBuffer() {
        // mutable & thread safe
        StringBuffer sb = new StringBuffer();
        int sbHashCode = System.identityHashCode(sb);

        sb.append("java");
        sb.append("spring");

        // 기존 StringBuffer 에 가변적으로 추가된다. 새 객체 생성 X
        assertThat(System.identityHashCode(sb)).isEqualTo(sbHashCode);
    }
}

Leave a comment