티스토리 뷰
jackson 을 이용할땐 나 같은 경우 항상 ObjectMapper 클래스를 이용했다. 그러다가 Serialization/Deserialization 에 각각 특화된 ObjectWriter/ObjectReader 클래스가 있다는걸 알게됐는데 ObjectMapper 를 사용하는 것과 어떤 차이가 있는지 알아보기로 했다.
먼저 ObjectMapper 의 가장 치명적인 문제는 스레드에 안전하지 않다는 것이다. 멀티 스레드 환경에서 공유 변수로 사용하게 될 경우 원치 않는 결과가 나올 수 있다. ObjectMapper 의 javadoc 에는 이렇게 적혀있다.
Mapper instances are fully thread-safe provided that ALL configuration of the instance occurs before ANY read or write calls. If configuration of a mapper instance is modified after first usage, changes may or may not take effect, and configuration calls themselves may fail. |
사용 전에 모든 설정이 완료된게 아니라면 문제가 생길 수 있음을 얘기하고있다. ObjectMapper 를 멀티 스레드 환경에서 사용시 문제가 생기는 케이스를 알아보자.
// 테스트 대상 클래스
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
// 테스트 코드
ObjectMapper objectMapper = new ObjectMapper();
Person person = new Person("LichKing");
ExecutorService es = Executors.newCachedThreadPool();
// 첫번째 스레드에서는 INDENT_OUTPUT 기능을 활성화 시키면서 json 출력
es.execute(() -> {
for(int i = 0; i < 10; i++) {
objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
String json = objectMapper.writeValueAsString(person);
System.out.println("new line!!\n" + json);
}
});
// 두번째 스레드에서는 INDET_OUTPUT 기능을 비활성화 시키면서 json 출력
es.execute(() -> {
for(int i = 0; i < 10; i++) {
objectMapper.disable(SerializationFeature.INDENT_OUTPUT);
String json = objectMapper.writeValueAsString(person);
System.out.println("one line!!\n" + json);
}
});
es.shutdown();
두 개의 스레드에서 ObjectMapper 를 공유하고, 내부 상태를 변경하면서 사용하고있다. 두 스레드에서 기대 출력값은 이렇다.
// INDENT_OUTPUT 활성화 new line!! { "name" : "LichKing" } // INDENT_OUTPUT 비활성화 one line!! {"name":"LichKing"} |
실제로 위 코드를 실행해보면 이렇게 출력되는 경우가 있었다. 멀티스레드 환경이니 환경별로 결과가 다를 수 있음을 유의하자.
// 기대와 다르게 출력 new line!! {"name":"LichKing"} one line!! {"name":"LichKing"} |
이는 ObjectMapper 가 내부 상태를 변경하는 가변 객체이기 때문에 발생하는 문제다. ObjectWriter/ObjectReader 는 이런 문제를 해결하고자 했고, 불변 객체이기 때문에 멀티 스레드에 안전하다.
ObjectMapper objectMapper = new ObjectMapper();
// 스레드간에 공유해서 사용할 객체
ObjectWriter objectWriter = objectMapper.writerFor(Person.class);
Person person = new Person("LichKing");
ExecutorService es = Executors.newCachedThreadPool();
es.execute(() -> {
for(int i = 0; i < 10; i++) {
String json = objectWriter.with(SerializationFeature.INDENT_OUTPUT)
.writeValueAsString(person);
System.out.println("new line!!\n" + json);
}
});
es.execute(() -> {
for(int i = 0; i < 10; i++) {
String json = objectWriter.without(SerializationFeature.INDENT_OUTPUT)
.writeValueAsString(person);
System.out.println("one line!!\n" + json);
}
});
es.shutdown();
ObjectWriter 는 불변 변수이기 때문에 with/without 메서드를 통해 상태를 변경하면 내부 상태 변경이 아니라 상태가 변경된 복사본을 반환한다. 그렇기 때문에 멀티 스레드에 안전할 수 있다. 또한 ObjectWriter 를 만들때 writerFor 라는 팩토리 메서드를 호출하면서 미리 타입을 전달하고 있기 때문에(writerFor(Person.class)) 성능에도 약간 이점이 있다고 한다. 하지만 큰 향상은 아닌 것 같으니 굳이 성능 때문에 꼭 써야한다까진 아닌 듯 싶다.
# 정리
ObjectMapper 가 멀티 스레드에 안전하지 않다고 얘기했지만, 거의 대부분의 상황에서 ObjectMapper 를 멀티 스레드 환경에서 이용하고 있을 것이다. 그럼 깜짝 놀라며 모든 코드를 다 바꿔야하나 싶은 생각이 들지만 ObjectMapper 의 설정을 동적으로 바꾸는게 아니라 초기에 한번 설정해놓고 serialization/deserialization 만 멀티스레드에서 하는거라면 사실 ObjectMapper 를 이용해도 문제가 없다. 실제로 나도 개발자 생활 해오면서 무수히 많은 ObjectMapper 를 만들었지만 멀티 스레드 환경에서 동적으로 설정을 변경하면서 하나의 인스턴스만 이용한 적은 한번도 없었다. 그런 케이스가 꽤나 특수한 케이스임을 생각해본다면 지금껏 사용해왔던 습관대로 사용해도 큰 문제는 없을 것으로 생각한다. 다만 멀티 스레드에서 설정을 변경해가면서 써야하는 상황이 온다면 ObjectWriter/ObjectReader 사용을 고려해보자. 그리고 특정하게 정해진 타입만 핸들링하는 경우에도 사용을 고려해보면 좋을 것 같다.
참고자료
- https://cowtowncoder.medium.com/jackson-api-beyond-objectmapper-22b6d3bbd1ef
'Java' 카테고리의 다른 글
~ByParameter naming 에 대한 고찰 (1) | 2022.11.03 |
---|---|
builder pattern 에 대한 고찰 (3) | 2022.04.23 |
ThreadPoolExecutor 사용시 maximumPoolSize 동작방식 (5) | 2021.02.02 |
UncaughtExceptionHandler 인터페이스 (0) | 2021.01.25 |
동기화 방법에 따른 데드락 상태에서 스레드 덤프 차이 (0) | 2020.03.29 |
- Total
- Today
- Yesterday
- toby
- clean code
- Kotlin
- code
- Jackson
- DesignPattern
- generics
- spring cloud
- EffectiveJava
- JPA
- http
- TEST
- Git
- OOP
- java8
- go-core
- java
- programming
- MySQL
- frontcode
- javascript
- JavaScript Core
- Design Pattern
- 정규표현식
- db
- backend개발환경
- frontend개발환경
- mariadb
- Spring
- servlet
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |