[Java] 멀티스레드와 동기화 In Java

RUNGOAT·2023년 4월 22일
0

Java

목록 보기
4/5

1️⃣   공유자원과 임계영역

  • 공유 자원: 여러 스레드가 동시에 접근할 수 있는 자원
  • 임계 영역: 공유 자원들 중 여러 스레드가 동시에 접근했을 때 문제가 생길 수 있는 부분
    이 때, 생길 수 있는 문제가 경쟁상태
  • 경쟁상태: 둘 이상의 스레드가 공유 자원을 병행적으로 읽거나 쓰는 동작을 할 때 타이밍이나 접근 순서에 따라 실행 결과가 달라지는 상황
    Ex) Read - Modify - Write, Check - then - act

1.1 경쟁상태

💬 Read - Modify - Write

  • 만약 동시에 30명이 수강 신청한다면

  • 테스트 결과 28명으로 나온다. 왜 그럴까
  • 스레드가 맞물렸기 때문!

💬 Check - then - act

  • 왜 80대의 숫자가 찍힐까
  • studentCount가 28인걸로 가정하고 아래 타임라인을 살펴보면
    if 분기문 통과 시점에 다른 스레드에서 studentCount + 1을 수행하여 경쟁상태가 발생한다.

이러한 경쟁상태를 방지하기 위해서 원자성과 가시성을 보장해야 한다.


2️⃣   원자성과 가시성

2.1 원자성

: 공유 자원에 대한 작업의 단위가 더 이상 쪼갤 수 없는 하나의 연산인 것처럼 동작하는 것

  • 예제와 같은 분리된 연산을 하나로 모아주는 과정 == 원자성

2.2 가시성

💬 예제

  • 미사일이 날아오면 요격하는 시스템
  • 2개의 스레드 존재
    • 미사일 감지하여 MissileLaunched를 true로 바꾼다.
    • MissileLaunched가 true가 되면 요격 미사일 발사한다.

  • 5초 후에 missileLaunched를 true로 바꾼다.
  • 그럼 5초 후 요격 메세지가 출력되어야 하지만 2분이 넘도록 요격이 출력되지 않는다.

가시성을 이해하기 위해서는 CPU Cache에 대해 이해해야 한다.
스레드가 실행하면 CPU를 실행한다.
스레드가 CPU를 실행할 때 Main memory에서 변수 값을 읽어와 스레드를 실행한다.
그런데 Main memory와 CPU 사이에 거리가 멀어 CPU Cache를 사용한다.
CPU는 스레드를 실행할 때 필요한 값을 Main memory에서 읽어와서 CPU Cache에 담아두고 CPU Cache의 모든 연산을 반영한 다음에 그 값을 Main memory에 덮어쓰는 방식으로 동작을 한다.

요격하는 스레드는 CPU Cache에 담긴 false라는 값을 계속 보고 있어서 2분이 넘도록 요격이 출력되지 않았다.

💬 Java 에서 가시성 보장하기 - volatile

  • Main memory에서만 값을 읽고 쓰고 CPU Cache를 사용하지 않는다.

정상적으로 값이 출력된다.


3️⃣   동기화

  • 원자성과 가시성을 챙기는 것을 동기화라고 한다.

3.1 블로킹

  • 특정 스레드가 적업을 수행한느 동안 다른 작업은 진행하지 않고 대기하는 방식
    Ex) Monitor, Synchroized 키워드

💬 모니터 메커니즘

  • Java에서 동기화를 하기 위한 도구
  • 배타동기는 synchroized, 조건동기는 wait(), notify(), notifyAll()
  • 임계영역에 하나의 한 번에 하나의 스레드만 락을 가지고 들어가도록 설계되어 있다.

  • 만약 하나가 작업을 하다가 wait() 연산을 만나면 해당 스레드는 슬립 상태가 되면서 조건동기 큐로 간다.

  • 그리고 대기 중이던 배타동기 큐에 있는 다른 스레드가 임계 영역으로 들어간다.

  • 만약 notify() 또는 notifyAll()을 호출하면 슬립 상태의 스레드를 깨워서 임계 영역이 비웠을 때 작업을 수행하도록 한다.

💬 Synchronized

  • 배타동기를 선언하는 키워드
  • 연산결과가 메모리에 써질 때까지 다른 스레드는 대기

💬 Synchronized 동작 방식

  • 순차 접근하여 원자성 + 가시성 보장
  • 단점
    - 대기로 인한 성능 저하
    - 락을 가지고 임계 구역에 들어가기 때문에 DeadLock 발생 가능

💬 단점 - 데드락

가지고 있는 자원을 놓지 않고 상대방의 자원을 기다리는 상태를 데드락이라 한다.

예시)

  • 옷이 한 벌만 존재
  • 하나씩 입고 있는 상태에서 자신의 옷은 벗지 않고 상대방이 옷을 벗기를 서로 기다리고 있는 상태

💬 예제

  • 데드락이 걸린 상태로 10초 이후에 테스트가 종료된다.
    만약 데드락이 걸리지 않았다면 예외를 발생시켜 테스트기 실패한다.

3.2 논블로킹

  • 다른 스레드의 작업 여부와 상관없이 자신의 작업을 수행하는 방식
    Ex) Atomic 타입

💬 CAS - Compare And Swap 또는 Compare And Set

  • 논블로킹을 이해하기 위한 알고리즘

💬 Atomic 타입

  • 동시성을 보장하기 위해 Java에서 제공하는 Wrapper class
  • CAS + Volatile

  • volatile을 통해 Jvm에서 바로 값을 가져온다. (가시성)
  • CAS를 사용해 메모리에 저장된 값과 스레드 내부 기대값을 비교한다. (원자성)
  • 이를 통해 원자성과 가시성을 보장한다.

예시) AtomicInteger

  • 30번 요청 시 (앞서 나왔던 28번이 아닌) 정상적으로 30번이 나온다.


4️⃣   스레드 안전한 객체 설계 방법

여러 스레드가 동시에 클래스를 사용하려 하는 상황에서 클래스 내부의 값을 안정적인 상태로 유지할 수 있다.

4.1 종류

  • 너무 많다...
  • 하나하나 복잡하며 전략에 따라 선택할 것이 다르다.
  • 구현에 따른 장단점도 다르다.

4.2 간단하게 처리한다면


📌 출처

[10분 테코톡] 알렉스, 열음의 멀티스레드와 동기화 In Java

profile
📞피드백 너무나 환영

0개의 댓글