이번 시간에는 자바에서 문자열을 다루는 클래스인 String, StringBuffer, StringBuilder에 대해서 알아보겠습니다.
문자열을 다루는 클래스들을 나눠논 이유는 무엇일까요?
만약 문자열 연산횟수가 많아질 경우, 멀티스레드 환경일 경우, Race Condition의 상황이 발생할 경우 어떤 문자열 클래스를 사용할지 고려하지 않는다면, 문제가 발생할 수 있습니다.
String 클래스와 StringBuffer, StringBuilder의 차이점은 불변여부입니다.
String클래스는 불변하기 때문에, 만약 아래와 같이 문자열 연산을 하게 된다면 기존의 heap메모리에 생성되었던 "jungwoo"에 "kim"을 추가로 붙이는 게 아니라, 기존에 heap 메모리안 String pool에 생성된 "jungwoo"는 놔두고, 추가적으로 "jungwookim"이 heap메모리안 String pool에 생성되게 됩니다. 정리하자면, stack에 생성된 참조변수 string1은 기존의 heap 메모리안 String pool에 생성된 "jungwoo"를 가리키게 되는 게 아니라 heap 메모리안 String pool에 추가로 생성된 "jungwookim"을 가리키게 되는 것입니다.
추가적으로, new String 키워드를 이용해 문자열을 생성되면 기존에 "jungwoo"나 "jungwookim"이 heap메모리 안의 String pool에 생성되었던 것과 달리, "seongsik"이 heap메모리에 따로 생성되는 것을 알 수 있습니다! 이렇게 되면 추후, "seongsik"이라는 문자열을 또 생성할 때 heap메모리에 추가로 생성되므로 메모리의 낭비가 일어나겠죠
그래서 이를 보완하기 위해 String pool을 사용하고, "jungwoo"문자열을 참조하는 참조변수 strirng3을 만들어도 heap메모리 안에 "jungwoo"가 추가적으로 생성되지 않게 됩니다.
지금까지 위의 코드를 바탕으로, jvm이 관리하는 메모리 영역으로 그림을 통해 알아보겠습니다.
정리하자면, String 클래스로 여러 문자열 연산을 하게 된다면 heap메모리에 데이터가 쌓여 메모리의 낭비가 일어나고 가비지 콜렉터가 정리하기 위한 가비지들이 생성되어 애플리케이션 성능에 영향을 줄 수 있습니다.
그렇다면, StringBuffer, StringBuilder는 이를 어떻게 해결할 수 있을까요?
StringBuffer, StringBuilder는 String클래스와 달리 가변입니다. 또한, String pool에 데이터가 생성되는게 아닌 heap 메모리에서 따로 생성됩니다.
기존에 "jungwoo"가 heap메모리에 생성되고, append메서드를 이용해 동일 객체 내에서 "jungwookim"으로 변경할 수 있습니다.
StringBuilder또한 사용법은 StringBuffer와 비슷하지만, StringBuffer는 동기화를 지원, StringBuilder는 동기화를 지원하지 않는다는 차이점이 있습니다.
따라서, StringBuffer는 동기화를 지원해서 멀티스레드 환경에서 안전한 대신에 StringBuilder에 비해 동기화를 지원하므로 성능이 비교적 느리고, StringBuilder는 동기화를 지원하지 않아서 멀티스레드 환경에서 안전하지 않지만 StringBuffer와 달리 성능이 비교적 빠르다는 장점이 있습니다.
정리하자면, 자바에서 문자열을 사용할 때 문자열 연산이 많다면 String 클래스 대신에 StringBuffer, StringBuilder를 이용하는 것이 바람직하고, 멀티스레드 환경이라면 Race Condition(공유자원에 스레드가 동시에 접근할 때 결과값에 영향을 주는 것)이 발생할 수 있어 StringBuffer, 단일 스레드 환경이라면 성능을 올리기 위해 StringBuilder를 사용하는 것이 바람직합니다.
참고로, 여기서 멀티스레드란 무엇일까요.?
멀티스레드란 스레드가 멀티로 있는건데, 스레드란 프로세스 하나에서 실행의 흐름단위를 말하고, 프로세스란 실행중인 프로그램,, 을 의미합니다. 예를 들어 알아보겠습니다. 멀티스레드를 활용한다면 첫번째로 멀티스레드를 이용해 프로세스의 응답성을 끌어올릴 수 있습니다. 만약, 웹 브라우저에서 주소를 치게 되면 제일먼저 html문서가 날아오고 웹 브라우저 화면에 렌더링을 하려고 봤더니 여러가지 이미지들이 있습니다. 웹 브라우저가 해석을 해서 이미지를 다시 웹 서버에게 요청을하는데, 이미지들이 다 도착하면 html화면에 텍스트도 넣고, 이미지도 넣고 하나의 웹 페이지를 완성해서 보여줍니다. 이러한 과정에서 html문서를 읽어온 다음에 이미지들을 다시 웹 서버에 요청을 하는데요, 오래걸리는 I/O입출력 작업이기 때문에 프로세스가 cpu를 자진반납해서 운영체제가 해당 프로세스를 block시킵니다. -> 이렇게 되면 사용자 입장에서 답답하기 때문에 멀티스레드를 이용합니다. 이미지들을 웹 서버에 요청한 순간에 프로세스를 블럭시키는게 아니라, 네트웍 요청을 한 스레드만 블럭이 되고 다른 스레드가 이미 읽어온 html문서라도 보여줍니다. 비동기식 입출력을 이용해서 말이죠!
두번째로, 자원공유를 할 수 있습니다. 똑같은 일을 하는 프로세스를 만들어 메모리 공간을 따로 만드는 게 아니라, 하나의 프로세스를 만들고 그 안에 CPU 수행 단위만 여러개 두게 되면 각종 자원은 스레드들이 공유를 하게 되기 때문에 자원을 효율적으로 사용할 수 있습니다.
세번째로, 경제성입니다. 프로세스를 하나 만드는건 오버헤드가 크지만, 프로세스 하나에 스레드를 하나 추가하는 건 오버헤드가 크지 않습니다.
네번째로, 멀티프로세스 활용입니다. cpu가 여러개 있으면 각각의 스레드가 서로 다른 cpu에서 병렬적으로 일을 할 수 있기 때문에 멀티 프로세서 환경에서 조금 더 효율적으로 실행할 수 있습니다.
