JVM 동시성 프로그래밍 딥다이브 5. Synchronization Fundamentals 동기화 개념 - 싱글/멀티 스레드 & 동기화

2026. 2. 15. 10:14·기술 학습/JVM 스레드 딥다이브

싱글 스레드 & 멀티 스레드

개요

  • 프로세스는 오직 한개의 스레드로만 구성되는 싱글 스레드 프로세스와 하나 이상의 스레드로 구성되는 멀티 스레드 프로세스로 구분된다.
  • 스레드의 선택 기준은 어떤 방식이 더 효율적으로 자원을 사용하고 성능처리에 유리한가에 있다.

싱글스레드 장점

- 문맥교환 비용이 없다.

- 동기화 이슈가 없다.

- 자원 비용이 적다.

 

멀티스레드 장점

- 동시성으로 사용자 응답성이 향상된다.

- CPU 멀티코어의 병렬성으로 성능이 향상된다.

- 한 스레드의 오류가 다른 스레드로 전파되지 않는다.

 

멀티스레딩과 동시성

  • CPU의 동시적 작업 처리는 CPU 코어 개수보다 스레드의 개수가 많을 때, 즉 멀티스레딩 환경에서 자원을 효율적으로 배분하고 사용하기 위해 설계된 방식이다.
  • 같은 프로그램 안에서 실행되는 여러 스레드가 읽기/쓰기 작업을 같은 메모리 영역에서 동시에 실행할 때 동시성 문제가 야기된다.

동시성 문제란?

하나의 스레드가 어떤 메모리 영역의 데이터를 사용중인데 다른 스레드가 같은 메모리 영역의 데이터를 읽거나 쓸 경우에 발생되는 문제이다.

 

동시성으로 인해 알아야할 동기화 관련 주제

o Critical Section

o CPU Process Architecture

o Thread-safety

o Mutual Exclusion

o Semaphore

o Monitor

o Race Condition

o Deadlock

o Starvation

o Volatile

o Synchronized

o wait / notify

o SpinLock / Busy Waiting

o Lock

o Condition

o CAS (Compare and Swap)

o Atomic Variables

 

코드예시

싱글 스레드

fun main() {
    val start: Long = System.currentTimeMillis()
    var sum = 0
    
    for (i in 1..5) {
        Thread.sleep(1000)
        sum += i
        if (i == 3) throw RuntimeException("의도적 에러 발생")
        println("작업 $i 완료")
    }

    println("단일 스레드 처리 결과: sum = $sum")
    val end: Long = System.currentTimeMillis()
    println("단일 스레드 처리 시간: ${end - start}ms")
}

  • 싱글 스레드인 경우 Main 스레드 홀로 프로그램이 수행된다.
  • 따라서 예외가 던져지는 경우 바로 메인 스레드가 중단되어 프로그램이 중단된다.

멀티 스레드

fun main() {
    val thread1 = Thread {
        var sum = 0
        for (i in 1..5) {
            Thread.sleep(1000)
            sum += i
            printWithThread("작업 $i 완료")
        }
    }

    val thread2 = Thread {
        var product = 1
        for (i in 1..5) {
            Thread.sleep(500)
            product *= i
            if (i == 4) throw RuntimeException("의도적 에러 발생 in 스레드 2")
            printWithThread("작업 $i 완료")
        }
    }

    thread1.start()
    thread2.start()
    thread1.join()
    thread2.join()
}

  • 멀티스레드의 경우 복수의 플랫폼 스레드가 생성되어 작업을 수행하게 된다.
  • 메인 스레드가 아닌 별도로 생성된 스레드가 중단되는 것이므로 메인 스레드는 끝까지 작업을 완료할 수 있다.
  • 즉, 스레드의 오류가 다른 스레드로 전파되지 않는 것을 확인할 수 있다.

동기화와 CPU 관계

동기화란?

- 프로세스 혹은 스레드 간 공유 영역에 대한 동시접근으로 인해 발생하는 데이터 불일치를 막고 데이터 일관성을 유지하기 위해 순차적으로 공유 영역을 수행하도록 보장하는 매커니즘이다.

 

CPU 연산 처리 이해

- 모든 기계어 명령은 원자성을 갖는다. 하나의 기계어 명령어가 실행을 시작할 경우 그 명령의 수행 종료 시 까지는 인터럽트를 받지 않는다.

- CPU가 두 개 이상의 명령어를 처리할 경우에는 원자성이 보장되지 않는다. 이는 각 명령을 수행하는 중 OS가 다른 스케줄링으로 CPU에게 다른 명령을 수행하게 함으로서 현재 수행중인 명령을 인터럽트(중단)한다는 의미이다.

- 두 개 이상의 명령어를 원자성으로 묶기 위해서는 스레드 간 동기화 매커니즘이 필요하다. 즉, 한 스레드가 모든 명령을 다 수행될 때까지 도중에 중단되지 않도록 해야한다.

{
data = data + 1
}
--- 기계어 변환 ---
{
LOAD R1, data <---- 하나의 기계어 명령어. 원자성 보장. 동시성 문제 없음.
ADD R1, 1
STORE R1, data
} <---- 두 개 이상의 기계어 명령어. 원자성 보장 안됨. 동시성 문제 발생 가능. 동기화 필요.

 

동기성 문제 예시 코드

fun main() {
    var count = 0

    val thread1 = Thread {
        for (i in 1..10000) {
            count++
        }
    }

    val thread2 = Thread {
        for (i in 1..10000) {
            count++
        }
    }

    thread1.start()
    thread2.start()
    thread1.join()
    thread2.join()

    printWithThread(count)
}

  • 1만번 카운팅 루프를 2개의 스레드에서 수행했으니 2만이 되어야 한다.
  • 하지만 위에 기계어 변환 코드처럼 동시성 이슈가 발생되어 누락되는 카운트가 발생되는 동시성 문제가 발생되어
    2만보다 적은 수가 카운팅 되었다.

Critial Section 임계영역

Critial Section이란?

- 둘 이상의 스레드가 동시에 접근해서는 안되는 공유 자원에 접근하는 코드 영역을 말한다.

- 임계 영역은 entry section, critical section, exit section, remainder section으로 구성된다.

 

입장 영역, 임계 영역, 퇴장 영역, 나머지 영역 예시

- 입장영역: lock.lock()

- 임계영역: count++

- 퇴장영역: lock.unlock()

- 나머지영역: log.info(count)

 

Critical Section Problem

  • 한 스레드가 임계 영역을 실행하고 있을 때, 다른 스레드가 같은 임계 영역을 사용함으로서 발생하는 문제.
  • 이 문제를 해결하기 위해서는 3가지 충족 조건이 요구된다.
    • Mutual Exclusion (상호 배제): 임계 구역을 실행중이면 다른 스레드는 동일 임계 구역을 실행할 수 없음.
    • Progress (진행): 임계 구역에 실행중인 스레드가 없고, 진입하려는 스레드가 있을 때 어떤 스레드가 들어갈 것인지 선택해줘야 하며 결정이 무한정 미뤄져선 안된다.
    • Bounded Waiting (한정대기): 다른 스레드가 임계 구역에 들어가도록 요청하고 수락 되기 전에, 기존 스레드가 임계영역에서 실행할 수 있는 횟수에 제한이 있어야 한다. 기아상태가 발생하지 않도록 해야한다.

동기화 도구들

  • 뮤텍스, 세마포어, 모니터, Compare And Swap(CAS) 와 같은 동기화 도구들을 통해 Critical Section 문제가 발생하지 않도록 할 수 있다.
  • 자바에서는 synchronized 키워드 부터 여러 동기화 도구를 제공한다.

 

코드예시

fun main() {
    val sharedResource = SharedResource()

    val thread1 = Thread {
        for (i in 1..100) {
            sharedResource.increment()
        }
    }

    val thread2 = Thread {
        for (i in 1..100) {
            sharedResource.increment()
        }
    }

    thread1.start()
    thread2.start()

    thread1.join()
    thread2.join()
}

class SharedResource {
    private var counter = 0

    fun increment() { // Entry Section
        synchronized(this) {
            // Critical Section
            counter++

            printWithThread("Counter: $counter")
        } // Exit Section

        doWork() // Remainder Section
    }

    private fun doWork() {
        // Simulate some non-critical work
        Thread.sleep(1)
    }
}

  • 임계영역에 대한 Entry, Critical, Exit, Remainder 영역에 대해 확인해 볼 수 있다.

안전한 스레드 구성

안전한 스레드란?

  • 여러 스레드에서 클래스나 객체에 동시에 접근해서 계속 실행하더라도 지속적인 정확성이 보장되는 코드.
    이를 "스레드 세이프(Thread Safe) 하다"고하며, 곧 "스레드에 안전하다"고 하는것이다.
  • 기본적으로 클래스 명서에 스레드 안정성을 헤치는 코드나 상태를 갖고 있지 않으면 스레드에 안전하다고 정의할 수 있다.
  • 스레드에 안전한 코드에는 경쟁 상태가 없으며 경쟁 상태는 다수의 스레드가 공유자원에 쓰기작업을 시도할 때 발생한다.
    따라서, 스레드가 실행될 때 어떤 자원을 공유하는지 아는것이 중요하다.

스레드에 안전한 구조

  • 임계영역을 동기화 한다.
    • 동시에 여러개의 스레드가 임계영역에 접근하지 못하도록 Lock 매커니즘을 사용한다.
  • 동기화 도구를 사용한다.
    • 세마포어, CAS, Atomic 변수, 동시성 자료구조 (Thread Safe 자료구조)들을 사용한다.
  • 스레드의 스택에 한정해서 상태를 관리한다.
    • 스레드마다 할당된 스택 메모리 내에서 상태를 관리한다. 이를 통해 다른 스레드와 상태를 공유할 수 없도록 함.
  • ThreadLocal을 사용한다.
    • 스레드마다 갖는 전용 저장소인 ThreadLocal을 사용해서 상태를 관리한다.
  • 불변 객체를 사용한다.
    • 객체의 상태를 변경할 수 없는 클래스를 사용하거나 상태를 변경할 수 없도록 클래스를 만들어서 스레드에 안전하게 한다.

 

스레드의 스택 한정

지역 변수

  • 기본형 지역 변수는 스레드 마다 독립적으로 가지고 있는 스택에 저장되기 때문에 스레드간에 공유될 수 없다. 스레드에 안전한다.
  • 메서드로 전달되는 기본형 파라미터 변수도 스택에서만 관리되므로 스레드에 안전하다
반응형
저작자표시 비영리 변경금지 (새창열림)

'기술 학습 > JVM 스레드 딥다이브' 카테고리의 다른 글

JVM 동시성 프로그래밍 딥다이브 4-2. 스레드 활용 - ThreadLocal  (0) 2026.02.01
JVM 동시성 프로그래밍 딥다이브 4-1. 스레드 활용 - 예외처리, 유저/데몬 스레드, ThreadGroup  (0) 2026.01.18
JVM 동시성 프로그래밍 딥다이브 3. 스레드 기본 API - Sleep, Join, Interrupt 등  (1) 2026.01.04
JVM 동시성 프로그래밍 딥다이브 2. 스레드 생명주기와 상태  (0) 2025.12.21
JVM 동시성 프로그래밍 딥다이브 1. 프로세스 & 스레드  (1) 2025.12.07
'기술 학습/JVM 스레드 딥다이브' 카테고리의 다른 글
  • JVM 동시성 프로그래밍 딥다이브 4-2. 스레드 활용 - ThreadLocal
  • JVM 동시성 프로그래밍 딥다이브 4-1. 스레드 활용 - 예외처리, 유저/데몬 스레드, ThreadGroup
  • JVM 동시성 프로그래밍 딥다이브 3. 스레드 기본 API - Sleep, Join, Interrupt 등
  • JVM 동시성 프로그래밍 딥다이브 2. 스레드 생명주기와 상태
구름뭉치
구름뭉치
구름의 개발일기장
  • 구름뭉치
    구름 개발일기장
    구름뭉치
  • 전체
    오늘
    어제
    • ALL (283)
      • 프로젝트 (23)
        • 토스페이먼츠 PG 연동 시리즈 (12)
        • JWT 방식 인증&인가 시리즈 (6)
        • 스우미 웹 애플리케이션 프로젝트 (1)
        • 스프링부트 기본 보일러 플레이트 구축 시리즈 (2)
        • 마이크로서비스 아키텍쳐 시리즈 (1)
      • 스프링 (43)
        • 스프링부트 API 설계 정리 (8)
        • 스프링부트 RestAPI 프로젝트 (18)
        • 스프링부트 WebSocket 적용기 (3)
        • 스프링 JPA 정리 시리즈 (5)
        • 스프링 MVC (5)
        • 스프링 배치 (2)
        • 토비의 스프링 정리 (2)
      • 기술 학습 (28)
        • 아파치 카프카 (9)
        • 클린 코드 (4)
        • 디자인 패턴의 아름다움 (2)
        • 모던 자바 인 액션 (7)
        • JVM 스레드 딥다이브 (6)
      • Web (25)
        • 정리글 (20)
        • GraphQL 정리글 (2)
        • Jenkins 정리글 (3)
      • 취업 (6)
      • CS (77)
        • 네트워크 전공 수업 정리 (11)
        • OSI 7계층 정리 (12)
        • 운영체제 정리 (19)
        • 데이터베이스 정리 (5)
        • MySql 정리 (17)
        • GoF의 Design Pattern 정리 (12)
      • 알고리즘 (70)
        • 백준 (56)
        • 프로그래머스 (12)
        • 알고리즘 정리본 (1)
      • 기초 지식 정리 (2)
      • 일상 (8)
  • 블로그 메뉴

    • 홈
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    레이저
    키보드 손목 받침대
    mx master s3 for mac
    부다페스트
    마우스
    크로아티아
    류블라냐
    마우스 패드
    동유럽
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.6
구름뭉치
JVM 동시성 프로그래밍 딥다이브 5. Synchronization Fundamentals 동기화 개념 - 싱글/멀티 스레드 & 동기화
상단으로

티스토리툴바