알고리즘 스터디 내 팀원분께서 System.out.println을 사용하지 않고, StringBuilder만을 사용하는 것을 보았다.
여쭈어보니, 본인에게 익숙해서 라는 답변을 들을 수 있었다.
개인적으로 무슨 차이가 있을까라는 생각으로 아래 학습한 내용을 작성한다.
System.out.println 왜 줄여야 하지??
인터넷에 나와있는 것을 그대로 보는 것도 좋겠지만, 직접 눈으로 확인하면 더 기억에 잘 남게 된다.
코드를 까보는 데에 익숙하지는 않지만, 이것 또한 내가 추구하는 가치를 알릴 밑거름으로 나를 성장시켜줄 것이다.
두렵지만 코드를 까보도록 하자
synchronized 라는 것이 보인다. 찾아보니 이건 동기화를 의미한다고 한다.
동기화란??: 공유 데이터를 작업 중인 스레드가 마칠 때까지 다른 스레드에서의 접근을 막는 것
참고) 하나의 프로세스에는 하나의 스레드가 존재한다. 스레드를 통해서는 같은 프로세스에서 데이터 공유가 가능하다.
아하! 동기화가 적용되있어서 지금 작업 중인 스레드가 마칠 때까지 저렇게 다른 스레드들한테는 대기시간이 발생하는 구나!!
정리해보면 아래와 같다.
System.out.println 쓰지 말라는 이유
1. 동기화로 인해 스레드 간 대기시간 발생한다.
2. 이로 인해 오버헤드 발생한다.
3. 특정 쓰레드를 기다려야하기 때문에 처리 속도와 효율 측면에서 성능 저하를 일으킨다!!
오버헤드: 어떤 처리를 하기 위해 들어가는 간접적인 처리 시간/메모리 등
예를 들어 A라는 처리를 단순하게 실행한다면 10초 걸리는데, 안전성을 고려하고 부가적인 B라는 처리를 추가한 결과 처리시간이 15초 걸렸다면, 오버헤드는 5초가 된다. 또한 이 처리 B를 개선해 B'라는 처리를 한 결과, 처리시간이 12초가 되었다면, 이 경우 오버헤드가 3초 단축되었다고 말한다.
그럼 출력할 때 StringBuilder는 왜 써도 되는거지??
보통 아래처럼 많이 쓰곤 했다.
방법 1. System.out.println로만 출력하기
for(int i=0;i<100000000;i++) {
System.out.prtinln(i);
}
방법 2. StringBuilder 활용하기
StringBuilder sb = new StringBuilder();
for(int i=0;i<100000000;i++) {
sb.append(i + "\n");
}
System.out.println(sb.toString());
음... 그런데.. 괜히 StringBuilder 에 append().. 같이 많이 안쓰고,
그냥 아래 예시처럼 계속 단순하게 + 해서 더하면 안될까??
String student_1 = "정윤수";
String student_2 = "자바";
StringBuilder sb = new StringBuilder();
sb.append(student_1 + student_2)
System.out.println(sb); //정윤수자바
그런데 앞으로 위처럼 쓴다고 가정하면..
만약에 수천명의 학생이 있으면..? 아래처럼 쓰는 게 정말 맞는걸까??
for(int i=0;i<300000000;i++) {
System.out.println(i);
}
student_1 + student_2 + ... + student_3000
// 이럴 때 append를 써주게 되면..??
sb.append(student_1 student_2 + ... + student_3000)
System.out.println(sb.toString());
찾아보니, 위 처럼 + 연산자는 String 을 더할 때마다 새로운 객체를 할당시킨다고 한다.
String student_1 으로 생성한 student_1 변수 자체는 String 객체가 아니다.
stdent_1 변수는 메모리에 있는 "정윤수"라는 값의 reference(참조) 일 뿐이다.
Java에서 String 객체는 메모리 절약과 Thread Safe하기 위해 설계한 Immutable 타입이기 때문에 + 연산자를 하게 되면 계속해서 새로운 객체에 값이 부여돼서 생성되게 된다는 것이다!!
즉, 새로운 메모리 할당을 많이 하게 돼서 메모리 낭비로 이어진다!
그런데, 그럼에도 쓰는 이유가 뭘까??
- StringBuilder의 대표적인 append쪽을 까보도록 해보자!!
-> 뭔가 Object를 받아서 String 으로 변환해 append()로 전달하고 있는데 아직 잘 모르겠다..
따라 가보자!
-> 솔직히 자세히는 모르겠지만, 뭔가 변환된 String 데이터의 길이를 뽑아내서 `ensureCapcityInternal` 로 보내고 있다는 것 정도는 알아볼 수 있다!
ensureCapcityInternal 쪽도 한번 확인해보자
허걱 아하!!! 위 주석의 설명을 읽어보면,
최소 capcity 값이 양수면, 해당 메서드는 용량 확인을 아래와 같이 하지만, 동기화를 시키지 않는다고 한다!!
즉, 아래와 같이 위 내용들을 정리할 수 있겠다!!
- System.out.println을 안쓰는 이유: 동기화로 인해 스레드 간 대기시간 발생 → 오버헤드 발생 → 성능 저하가 발생
- StringBuilder를 쓰는 이유: StringBuilder의 append 메서드를 통해 동기화가 발생하지 않아 메모리 낭비가 적다.
- String 객체 사용 시, +연산자를 사용하면 안되는 이유: String 객체는 Immutable 타입이기 때문에 + 연산자를 하게 되면 계속해서 새로운 객체에 값이 부여된 채 생성되게 되어 메모리 낭비로 이어질 수 있다.
참고
[James Gosling 인터뷰] https://www.artima.com/articles/james-gosling-on-java-may-2001#part13
'Java' 카테고리의 다른 글
JVM (0) | 2023.07.10 |
---|---|
Java Exception ( feat. Checked, UnChecked ) (0) | 2023.06.26 |
Java (0) | 2023.06.23 |
Java - equals, hashCode() (0) | 2023.06.20 |