"이미 참여한 휴대폰 번호입니다." 라고?? - 초보 개발자가 반드시 알아야 할 Thread Safe와 동…

페이지 정보

  • 조회 23
  • 작성일 2026.02.27 15:29

1. 한 지붕 아래 여러 식구, 'Thread' 이야기

java 언어를 다루다 보면 'Thread(스레드)'라는 단어를 한 번쯤 들어보셨을 거예요. 
쉽게 비유하자면, 웹 서버라는 큰 집에서 동시에 일을 처리하는 '일꾼'들이라고 생각하면 편해요.

우리가 만든 사이트에 접속자가 많아지면, 구글이나 네이버 같은 서버는 수많은 일꾼(Thread)을 내보내 각자의 요청을 처리하게 합니다. 

덕분에 우리는 여러 사람이 동시에 접속해도 기다리지 않고 서비스를 이용할 수 있죠. 

하지만 이 일꾼들이 '같은 도구(공유 자원)'를 동시에 쓰려고 할 때 문제가 시작됩니다.
 

2. "분명 처음 응모한 휴대폰인데..." 나를 괴롭혔던 그 에러

제가 예전에 이벤트 사이트를 개발했을 때의 일입니다.

이벤트 참여 버튼을 누르면 휴대폰 번호를 체크해서 중복 참여를 막는 로직이었는데,

문제가 생겨 처음 참여하는 사람인데도 자꾸 "이미 참여한 휴대폰 번호입니다"라는 에러가 발생하는 겁니다.

'x됐다....'

수시간을 씨름하다 찾아낸 결론은,

범인은 바로 동기화 처리가 되지 않은 Map<String, Object> param 필드였습니다.

  • 사건의 전말: 1번 일꾼이 A라는 사람의 번호를 Map에 담아 검사하는 도중, 2번 일꾼이 B라는 사람의 번호를 같은 Map에 덮어써 버린 거죠.

  • 결과: 1번 일꾼은 분명 A의 정보를 처리 중이었는데, 결과는 B의 중복 체크 결과를 반환하게 됩니다.

동시 접속자가 몰릴수록 이런 데이터 꼬임 현상은 심해졌고, 제 메일함은 컴플레인으로 폭발 직전이었답니다. 

이때 필요한 개념이 바로 Thread Safe(스레드 안전)입니다.

다음은 해당 jsp 소스와 java 소스입니다.

<%!
MultipartRequest req;
Map param;
Map qparam;
%>
<%
try {
 
    req = new MultipartRequest(pageContext);
 
} catch (FileUploadException ex) {
    ...
} catch (Throwable th) {
    ...
}
 
param = req.getParameter();
qparam = req.getQueryParameter();
...
%>

위에 <%! ... %> 선언부가 보이죠? 우선 여기 들어가는 변수들은 필드(멤버변수)가 되게 됩니다.

나중에 컴파일된 jspService 구조를 이해하실 수 있을 겁니다.

이 필드들은 여러 쓰레드(쉽게 말해 동시접속자) 가 접근했을 때 쓰레드에 취약하게 됩니다.

public class MultipartRequest extends HttpServletRequestWrapper {
    private final Map parameter;
    ...
 
    //생성자
    public MultipartRequest(PageContext pageContext) {
        여기서 param 세팅 
        (동기화처리 안해서 문제 발생!)
    }
}

마찬가지로 이번엔 더 위험한 소스입니다.

필드로 선언한 parameter 이놈 정말 위험합니다.

반드시 동기화 처리를 거쳐야 합니다. 

그렇지 않으면, 수많은 스레드들이 접근해서 구워삶아 유효하지 않은 객체가 될겁니다.
 

3. Thread Safe란 무엇이고, 왜 해야 할까?

Thread Safe는 여러 일꾼이 동시에 같은 데이터에 접근하더라도, 

데이터가 오염되지 않고 계산 결과가 늘 정확하게 유지되는 상태를 말해요.

만약 동기화를 구현하지 않는다면, 여러분의 소스코드는 언제 터질지 모르는 시한폭탄과 같습니다. 

사용자의 돈이 걸린 결제 시스템이나, 방금 제 사례처럼 중요한 개인정보를 다루는 곳에서 

동기화 오류가 난다면... 정말 아찔하죠?

이해를 돕기 위해 아주 쉬운 코드 예제 두 가지를 살펴볼게요.
 

❌ 예제 1: 위험한 소스코드 (동기화 없음)


public class DangerCounter {
    private int count = 0;

    public void addCount() {
        // 여러 스레드가 동시에 접근하면 숫자가 꼬여요!
        count++; 
    }
}

✅ 예제 2: 안전한 소스코드 (synchronized 사용)

synchronized 키워드를 붙여주면, 한 번에 한 명의 일꾼만 이 메서드를 쓸 수 있게 줄을 세워줍니다.

public class SafeCounter {
    private int count = 0;

    // 한 번에 하나의 스레드만 접근 가능!
    public synchronized void addCount() {
        count++;
    }
}

사실 제가 겪었던 Map 문제라면 ConcurrentHashMap 같은 전용 클래스를 쓰는 것이 효율적이고 안전합니다.
이 내용은 추후에 기회가 되면 올려보겠습니다.
 

4. 안전한 코딩은 선택이 아닌 필수입니다

동기화는 단순히 "알면 좋은 기술"이 아닙니다. 

서비스를 운영하는 개발자로서 사용자의 데이터를 보호하기 위한 최소한의 안전장치예요.

안일하고 가볍게 생각해 평소 처럼 배포했던 소스코드가 

수많은 사용자에게 "이미 참여한 번호입니다"라는 황당한 경험을 줄 수 있습니다.

내가 작성한 코드에 여러 일꾼이 동시에 달려들어도 안전한지(Thread Safe), 

공유 자원을 건드리는 곳에 적절한 동기화 처리가 되어 있는지 항상 의심하고 확인하세요.

여러분의 코드가 시한폭탄이 될지, 튼튼한 성벽이 될지는 바로 이 '동기화' 한 줄에 달려 있습니다!

[이 게시물은 이재민님에 의해 2026-03-04 13:53:46 개발에서 이동 됨]

댓글목록

등록된 댓글이 없습니다.