동시성(Concurrency)는 백엔드를 조금 더 깊게 구현하기 시작하면 직면하게 되는 문제이다. 이전 글에서 스레드에 대한 개념을 설명을 한 적이 있었다. 스레드는.. 일을 하는 일종의 단위이다. 이렇게 말하면 너무 추상적인데, 만약 부엌에서 음식을 만든다고 할 때, 스레드는 요리사로 생각하면 이해가 쉽겠다.
기초적인 백엔드 서버 구현할 때에는 보통 스레드 하나(요리사 한 명)을 배치하여 작업을 하는 것이 일반적이다. 여러분도 입문하여 처음 프로젝트를 할 때, 대부분 스레드 1개짜리용 서버를 구현을 하였을 것이다.
하지만, 오늘날 서버는 동시에 막대한 양의 요청을 처리해야 한다. 특히 대규모 서비스의 경우 더더욱 그러하다. 요리사 한 명이 아무리 빠르게 음식을 만든다고 해도, 작업에는 한계가 있을 터.. 이럴 경우에는 요리사를 한 명 더 추가(스레드)를 하면 된다.
동시성(concurrency)와 병렬성(parallelism)은 사실 엄밀히 다르다
위의 내용을 통에서 "아 스레드를 여러 개로 작업하는 것은 병렬로 일을 동시에 하는 거구나"라고 이해를 하실 수 있다. 그러나, 엄밀히 말해 동시성과 병렬성은 다르긴.. 하다. 왜냐하면, 스레드를 여러 개를 만들어 놓는다고 해도, 실제 컴퓨터에서 물리적으론 여러 개가 동시에 작업하지 않을 수 있기 때문.
그러니까.. 우리의 cpu에는 코어가 있다. 아마 들어보셨을 것이다. 6코어라면 코어가 6개인 코어이고, 실제 물리적으로 6개의 작업을 각 코어에 할당하여 동시에 병렬적으로 처리할 수 있다. 만약 12개의 작업을 처리하기 위해 스레드 12개를 만들어 처리하도록 요청한다면? 그렇다면 각 코어가 2개의 스레드를 빠르게 바꿔가면서 처리를 하게 된다.
그렇다. 병렬성은 실제 물리적으로 여러 개가 처리된다는 것이며, 동시성은 논리적인 단위로 그 만큼 여러개가 동시에 처리된다고 설정해 놓은 것이다.

동시에 다루고 있는 대상이 만약 한 개라면?
동시성 문제는 바로 여기서 출발한다. 만약 두 개의 스레드가 index라는 하나의 변수를 다루는 작업을 각각 맡겼다고 가정해보자. 현재 index는 0이다. 각각 스레드가 idex의 현재 인덱스의 값을 참조하여 1씩 증가시킨다. 이렇게 될 경우, index의 값은 몇일까?

만약 동시성 문제를 정확히 처리하지 못하게 되면, index의 값은 정확이 무엇이 될 지 모르게 된다. 각 스레드 모두 0인 값을 참조한 다음 1로 증가시켜 버릴 경우, index는 1이 된다. 각 스레드가 미세한 시간 차로 참조시간이 다를 경우, 두 번째 스레드가 1인 값을 참조하여 2로 증가시켜 index의 값은 2가 될 수 있다. 왜냐하면 위에서 명시하였듯, 동시성은 엄밀히 말해 병렬적으로 같은 시각 같은 시간에 다발적으로 일어나지 않기 때문이다.
해결책
해결책은 당연히 있다. 그리고 여러가지이다. 이해 대해 짧게 요약하여 설명하고자 한다.
1. synchronized 이 키워드를 사용하면, 하나의 스레드만 실행하도록 강제한다. 아래 코드에서 increment()라는 메서드는 어느 한 스레드에서 참고하고 있을 경우, 다른 스레드에서 접근하지 못하게 되고, '참고하고 있는 스레드가 끝날 때 까지' wait, 즉 대기상태가 된다.
private int index = 0;
public synchronized void increment() {
index = index + 1;
}
2. Lock 으로 직접 잠그기, 혹은 아래의 코드와 같이 아얘 Lock을 사용하여 잠궈버리는 방법도 있다. 어느 한 스레드가 increment 메서드를 사용하면 다른 스레드가 접근하지 못하게 lock을 걸어버린다. 또한 그 한 스레드가 메서드를 완전히 실행한 수 lock을 unlock해야 잠금이 풀려 접근이 가능.
private Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
index = index + 1;
} finally {
lock.unlock();
}
}
3. Atomic(원자적 변수) 사용, 아래의 예시로는 AtomicInteger가 되겠다. 굳이 Integer 타입 뿐만 아니라, Boolean, Long 등의 타입도 있다. 먼저 원자적이라는 개념에 대해 설명을 하자면, 우리가 index를 더할 때 실제로는 3단계의 작업으로 이루어진다는 것을 생각하면 된다.
index 읽기(읽어보니 지금은 2이군..) => index 더하기(좋아 2를 기준으로 1을 더해 3을 만들자) => index 저장하기(3을 저장하자)
이러한 연속된 작업을 중에 다른 스레드가 끼어들지 못하도록 하여, 하나의 원자처럼 다루도록 하는 것이 원자성이다. 코드는 아래 예시가 있겠다..
private AtomicInteger index = new AtomicInteger(0);
public void increment() {
index.incrementAndGet();
}'TIL' 카테고리의 다른 글
| 개발자 면접 질문 리스트 정리 (백엔드 중심) (0) | 2025.12.20 |
|---|---|
| Java의 메모리 구조: 힙(heap)과 스택(stack)을 중심으로 알아보자 (1) | 2025.09.18 |
| OAuth란? 그리고 적용시 고려해야 하는 점 (20) | 2025.07.29 |
| 왜 백엔드에서는 Spring을 사용하는가? (12) | 2025.05.17 |
| 백엔드 개발자라면 당신이 알아야 할 것! OSI 7 계층 (0) | 2025.02.18 |