Thread-Unsafe한 객체의 특징은 아래와 같습니다.
- get/set method가 있어 멀티 스레드 환경에서 접근이 가능하며, 수정이 가능한(Mutable) 데이터를 가지는 객체
그렇다면 외부 라이브러리를 사용할 때 쓰이는 객체가 쓰레드 세이프한지 확인하는 방법은 무엇일까요?
- 해당 라이브러리의 API 문서를 확인합니다.
ex) HashMap의 경우 문서에 아래와 같이 표시가 되어있습니다.
Note that this implementation is not synchronized.
ConcurrentHashMap의 경우는 아래의 내용이 포함되어 있습니다.
However, even though all operations are thread-safe, retrieval operations do not entail locking, and there is not any support for locking the entire table in a way that prevents all access. This class is fully interoperable with Hashtable in programs that rely on its thread safety but not on its synchronization details.
- 멀티스레드 환경에서 테스트를 통해 확인하기
SimpleDateFormat 클래스에는 아래와 같은 설명이 있습니다.
Synchronization
Date formats are not synchronized. It is recommended to create separate format instances for each thread. If multiple threads access a format concurrently, it must be synchronized externally.
테스트 코드를 작성해서 확인해 보겠습니다.
public class SimpleDateFormatTest {
private SimpleDateFormat sdf;
private String[] dates;
@BeforeEach
public void setUp(){
sdf = new SimpleDateFormat("yyyy-MM-dd");
dates = new String[]{"2022-03-21", "2022-03-22", "2022-03-23"};
}
/**
* 싱글 스레드에서 SimpleDateFormat을 이용한 parse(), format() 메서드의 결과는
* 최초의 날짜 형식의 문자열과 일치
*/
@Test
public void parse_on_single_thread(){
//given
Date date = null;
String formatDate = null;
//when
try {
date = sdf.parse("2022-03-21");
formatDate = sdf.format(date);
} catch (ParseException e) { //체크 예외이르모 명시적으로 예외처리 해줘야 함
e.printStackTrace();
}
//then
assertNotNull(date);
assertNotNull(formatDate);
assertEquals(formatDate, "2022-03-21");
}
@Test
public void parse_on_multi_thread(){
//given: 테스트에 필요한 데이터인 픽스처를 셋팅한다.
Thread[] threads = new Thread[dates.length];
for(int i=0; i<dates.length; i++) {
threads[i] = new Thread(new TestRunnable(dates[i], sdf));
}
//when
for (Thread thread : threads) {
thread.start();
}
try {
Thread.sleep(30000);
} catch (InterruptedException e) { //체크 예외이므로 명시적으로 처리해줘야 함
e.printStackTrace();
}
}
class TestRunnable implements Runnable{
private String strDate;
private SimpleDateFormat sdf;
TestRunnable(String strDate, SimpleDateFormat sdf){
this.strDate = strDate;
this.sdf = sdf;
}
@Override
public void run() {
int rollingCnt = 1000;
for(int i=0; i<rollingCnt; i++){
assertEquals(strDate, getFormatDate(), i);
}
}
/**
* throw new NumberFormatException("multiple points"); / FloatingDecimal line 1890 / in ..32223222E2
* @return
*/
private String getFormatDate() {
String formatDate = "";
try {
Date date = sdf.parse(strDate);
formatDate = sdf.format(date);
} catch (ParseException e) {
e.printStackTrace();
}
return formatDate;
}
private void assertEquals(String expected, String result, int rollingIndex){
if(! expected.equals(result)){
String msg = "date conversion failed after " + rollingIndex
+ " iterations. Expected " + expected
+ " but got " + result;
throw new RuntimeException(msg);
}
}
}
}
위의 parse_on_single_thread 테스트 케이스를 실행하면 성공하지만,
parse_on_multi_thread 테스트 케이스를 실행 시 여러가지 예외가 발생합니다.
멀티 스레드 환경에서 어떤 SimpleDateFormat의 멤버 필드가 수정되어 예외가 발생하는지는 모르겠으나
예외의 종류는 2가지 정도로 확인했습니다.
NumberFormatException: multiple points
RuntimeException: date conversion failed after #{rollingIndex} iterations. Expected #{expected} but got #{result;}
- NumberFormatException 예외의 경우
"2022-03-21" 문자열을 SimpleDateFormat으로 parse 후 format 하는 과정에서 '-'을 파싱할 때 문제가 발생하는 것으로 보입니다. FloatingDecimal.class 1890 Line에서 예외를 던지고 있습니다. "multi points"라고 메시지를 표시한 건
입력되는 문자열에 소수점이 2개 이상이기 때문입니다(ex: ..32223222E2).
- RuntimeException 예외의 경우
상기 NumberFortmatException처럼 어떤 포맷과 관련된 SimpleDateFormat의 멤버 필드가 변경되어 정상적으로 파싱 및 포맷 처리가 되지 않는 것으로 보입니다.
참고
'[개발] 언어 > Java' 카테고리의 다른 글
Java Switch Case (0) | 2023.11.09 |
---|---|
SOLID (1) | 2023.10.30 |
자바 애노테이션이란? (0) | 2022.03.24 |
JAVA BigDecimal을 왜 그리고 어떻게 사용할까? (0) | 2021.06.23 |
The unknown errors occur in pom.xml when using STS4 (0) | 2019.08.03 |