본문 바로가기
Language/☕️Java

StringBuffer에서 Thread safe의 원리 및 Example Sample Code를 통한 고찰

by 발개발자 2022. 8. 16.
반응형

Java에서 StringBuilder StringBuffer 사용 선택 목적은 보통 단일 Thread인지 Multi Thread인지에 따라 갈린다.

그런데 본인이 생각한 Multi Thread환경에서의 기대 값과 실제 값이 달라서 어떤식으로 Thread safe 구현하고 있는지 예시코드를 작성하며 개념 정리해보고자 한다.

 

  Multi-Thread Test

StringBuilder

    public class SbTest implements Runnable {
        StringBuilder sb;

        public SbTest() {
            sb = new StringBuilder();
        }

        public void run() {
            addChar();
        }

        public void addChar() {
            for (inti = 0; i < 10000; i++) {
                sb.append("1");
                sb.append("0");
            }
        }

        public static void main(String[]args){
            SbTest sbTest = new SbTest();
            Thread threadOne = new Thread(sbTest, "ThreadOne");
            Thread threadTwo = new Thread(sbTest, "ThreadTwo");

            threadOne.start();
            threadTwo.start();

            try {
                threadOne.join();
                threadTwo.join();

            } catch (Exception e) {
                System.out.println(e.getMessage());
            }

            System.out.println("length:" + sbTest.sb.length());

        }
    }

기대 값이 40000 이지만 당연 Thread safe 객체가 아니기 때문에 Multi-Thread환경에서 값이 올바르게 더해지지 못한 모습이다.

 

StringBuffer

    public class SbTest implements Runnable {
        StringBuffer sb;

        public SbTest() {
            sb = new StringBuffer();
        }

        public void run() {
            addChar();
        }

        public void addChar() {
            for (inti = 0; i < 10000; i++) {
                sb.append("1");
                sb.append("0");
            }
        }

        public static void main(String[]args){
            SbTest sbTest = new SbTest();
            Thread threadOne = new Thread(sbTest, "ThreadOne");
            Thread threadTwo = new Thread(sbTest, "ThreadTwo");

            threadOne.start();
            threadTwo.start();

            try {
                threadOne.join();
                threadTwo.join();

            } catch (Exception e) {
                System.out.println(e.getMessage());
            }

            System.out.println("length:" + sbTest.sb.length());

        }
    }

이와 반대로 StringBuffer에서는 Thread safe 객체이기 때문에 기대 값인 40000 올바르게 출력되었다.

 

객체의 append에는 어떤 비밀이 숨어있는 것일까?

 

바로 syncronized 이다.

 

 

 

  append

StringBuilder

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

 

 

StringBuffer

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

 

Synchronized java에서 Multi Thread 환경에서 동기화를 제어 해야할 경우 사용된다.

Synchronized 메서드를 호출하였을 때, 자신이 포함된 객체에 lock 걸게 된다. 따라서 확실하게 동기화를 확실히 보장시켜주지만, 객체에 포함된 다른 모든 synchronized 접근까지 lock 걸게 된다. 이를 해결하기위해 synchronized block 있으며 이에 관해 자세한 내용은 아래의 블로그를 참조하면 좋을 같다.

 

투덜이의 리얼 블로그 :: Java의 동기화 Synchronized 개념 정리#1 (tistory.com)

 

Java의 동기화 Synchronized 개념 정리#1

Java의 동기화 -Synchronized 키워드의 사용 Java를 프로그래밍 하다면 multi-thread로 인하여 동기화를 제어해야하는 경우가 생깁니다. 그래서 흔히 Synchronized 키워드를 사용하는데요 그냥 multi-thread로 동

tourspace.tistory.com

 

그렇다면 Multi-Thread환경에서 StringBuffer 사용하면 Thread 순서대로 값이 들어갈까?

 

일단 현재 output 확인해보자.

 

 

StringBuilder

 

StringBuilder 당연 Thread non-safe 환경이니 밑줄 것처럼 Multi-Thread에서 동시에 값이 들어가 중복되는 데이터가 발생 하는걸 예상 했었다.

 

 

StringBuffer

 

그런데 StringBuffer 에서도 삽입되어야 하는 문자의 길이는 맞지만, 11 이나 00 같은 연속되는 문자열의 확인을 통해, Thread 순서대로 값이 들어가는게 아닌 확인했다.

 

 

 

  왜 이럴까?

아까 위에서 말한것 처럼 syncronized 해당 객체를 lock 건다.

즉, StringBuffer 자체에 lock 걸고 Thread one, Thread two에서는 각각 계속 append 하기 때문에 순서를 보장하지 않는 것이다. 결국 StringBuffer의 Thread Safe라는게 Thread의 순서를 보장하는 것은 아닌 것이다.

 

그럼 순서를 보장하기 위해서는 어떻게 해야할까?

StringBuffer를 메소드 내부에 선언하여 인스턴스를 생성하거나,

위의 소스의 경우 addChar 메서드 자체에 syncronized 사용하여 해당 SbTest lock 걸어 순서를 보장할 있다.

 

, 이렇게 사용할 경우 addcar에서 SbTest 클래스에 lock 걸어 StringBuilder 사용해도 값이 순서대로 들어가는 보장할 있다.

따라서, Thread one에서 addcar 사용하고 있기 때문에 Thread Two SbTest클래스의 lock 풀릴 까지 대기한 , StringBuilder 사용하게 된다.

public class SbTest implements Runnable {
    StringBuilder sb;

    public SbTest() {
        sb = new StringBuilder();
    }

    public void run() {
        addChar();
    }

    public synchronized void addChar() {
        for (int i = 0; i < 10000; i++) {
            sb.append("1");
            sb.append("0");
        }
    }

    public static void main(String[] args) {
        SbTest sbTest = new SbTest();
        Thread threadOne = new Thread(sbTest, "Thread One");
        Thread threadTwo = new Thread(sbTest, "Thread Two");

        threadOne.start();
        threadTwo.start();

        try {
            threadOne.join();
            threadTwo.join();

        } catch (Exception e) {
            System.out.println(e.getMessage());
        }

        System.out.println("length : " + sbTest.sb.length());
        System.out.println(sbTest.sb.toString());

    }
}

 

물론 해당 코드는 Sample코드이기 때문에, 실제 Multi Thread 환경에서 고려할 사항도 있을 것이며 지금처럼 무식하게 해결하는 방법 말고 다양한 방법이 있을 것이다. 일단 오늘은 StringBuffer StringBuilder Thread safe 원리와 보장범위를 살펴 보았고 값에 대한 순서를 보장해야할 경우에 대해 간단하게 해결방안을 살펴보았다.

 

그럼 이만 !

반응형

댓글