본문 바로가기
Course/JSCODE - 자바

4주차 스터디 노트

by Dev Lighthouse 2024. 9. 5.
320x100
320x100

동시성 프로그래밍

 


 

Answer

  • 동시성과 병렬성의 차이점을 말해주세요.

- 동시성: Concurrency

여러 작업들이 논리적으로 동시에 실행되는 것처럼 보이지만, 실제로는 짧은 시간동안 교차 실행되는 방식

싱글코어에서 한 스레드가 작업을 하다 잠시 멈추면 다른 스레드가 작업을 이어받는 방식으로 동작(Context Switching)

시분할(Time Sharing) 알고리즘을 통해 여러 작업이 동시에 진행되는 것처럼 보임

싱글코어->멀티스레드 처리

 

- 병렬성: Parallelism

여러 작업들이 실제로 물리적으로 동시에 실행되는 방식

여러 코어(멀티코어)가 있는 CPU에서 여러 작업이 동시에 각각의 코어에서 실행

멀티코어->멀티스레드 처리

 

 

  • Thread-Safe하다는 것이 무슨 의미인가요?

여러 스레드에서 동시에 접근해도 안전하게 동작하는 코드를 의미

여러 스레드가 어떤 객체나 코드를 동시에 접근하더라도 그 상태/결과가 예측 가능하며, 데이터 손상이 일어나지 않는다는 것을 보장

 

 

  • 가시성 문제와 원자성 문제에 대해 설명해 주세요.

 

- 가시성 문제 (Visibility Problem)

멀티스레드 환경에서 한 스레드가 변경한 데이터를 다른 스레드가 즉시 볼 수 없는 상황

CPU 캐시와 메인 메모리 간의 불일치로 인해 발생함

 

- 원자성 문제 (Atomicity Problem)

원자성 문제는 여러 연산이 하나의 단위 작업으로 처리되어야 할 때, 다른 스레드의 간섭으로 인해 작업의 일관성이 깨지는 현상

 

 

 

  • 가시성 문제에 대해 조금 더 자세히 설명해 주세요. 여러 스레드가 모두 한 CPU의 캐시 메모리를 읽으면 가시성 문제가 발생하지 않을 것 같은데, 어떻게 생각하시나요?

그렇게 생각할 수도 있을 것 같다. 하지만 실제로는 몇몇 이유로 가시성 문제가 여전히 발생할 수 있습니다

 

  1. 멀티코어 프로세서: 현대의 대부분의 컴퓨터는 멀티코어 프로세서를 사용합니다. 각 코어는 자체 캐시를 가지고 있어, 한 코어의 캐시에 있는 값이 다른 코어의 캐시나 메인 메모리와 일치하지 않을 수 있습니다.
  2. 캐시 일관성 프로토콜: 멀티코어 시스템에서는 캐시 일관성을 유지하기 위한 프로토콜이 있지만, 이 프로토콜이 즉시 모든 코어의 캐시를 업데이트하지는 않습니다. 따라서 일시적으로 불일치가 발생할 수 있습니다.
  3. 컴파일러 최적화: 컴파일러는 성능 향상을 위해 코드를 재배치하거나 최적화할 수 있습니다. 이로 인해 프로그래머가 의도한 순서와 다르게 메모리 연산이 실행될 수 있습니다.
  4. CPU 내부의 비순차적 실행: 현대의 CPU는 성능 향상을 위해 명령어를 비순차적으로 실행할 수 있습니다. 이로 인해 메모리 연산의 순서가 바뀔 수 있습니다.

 

  • 자바의 동시성 이슈를 해결하는 방법을 아는만큼 설명해 주세요.

동기화 매커니즘을 사용해서 동시성 이슈를 해결할 수 있습니다

더 자세히 말씀드리면

a) synchronized 키워드: 메서드나 블록에 사용하여 한 번에 하나의 스레드만 접근할 수 있도록 함

b) volatile 키워드: 변수의 가시성을 보장

c) java.util.concurrent 패키지: 다양한 동기화 도구를 제공(Lock, Semaphore, ConcurrentMap, CountDownLatch 등)

d) Atomic 클래스들: 원자적 연산을 지원하는 클래스 사용

e) ThreadLocal: 각 스레드에 고유한 변수를 제공

f) Immutable 객체 사용: 불변 객체는 스레드 안전함

Lock 구현체

 

 

  • volatile 키워드가 무엇인가요?

volatiole: 변수의 가시성을 보장하는 키워드

 

volatile 키워드는 Java 변수를 "메인 메모리에 저장하겠다"라고 명시하는 것

[ 쓰레드 간 데이터 일관성을 유지하기 위해 메모리 장벽(Memory Barrier)을 설정 ]

a) 가시성 보장: volatile 변수에 대한 모든 읽기는 항상 가장 최근에 쓰여진 값을 보게 됨

b) 재배치 금지: volatile 변수에 대한 읽기/쓰기 연산은 다른 메모리 연산과 재배치되지 않음

c) 원자성 미보장: volatile은 복합 연산(예: i++)의 원자성을 보장하지 않음

volatile은 주로 flag 변수나 단순한 상태 값에 사용되며, 복잡한 동기화에는 적합하지 않음

 

멀티스레드 환경에서 각 CPU 코어는 자신의 캐시 메모리를 가지고 있음

캐시 메모리는 메인 메모리에 비해 매우 빠른 속도로 접근할 수 있지만, 캐시<->메인 메모리 간의 데이터 일관성이 항상 즉시 유지되지 않음

즉, 한 스레드가 캐시 메모리에 저장된 값을 수정하면, 그 값이 즉시 메인 메모리에 반영되지 않을 수 있음

다른 스레드가 그 값을 읽을 때, 여전히 메인 메모리나 자신의 캐시 메모리에 있는 오래된 값을 읽을 가능성이 존재

 

volatile 변수에 쓰기(write) 연산시, 변경된 값이 메인 메모리에 즉시 기록되고, 다른 스레드가 메인 메모리에서 값을 읽을 수 있음

또한, volatile 변수를 읽을 때는 최신 값을 캐시가 아닌 메인 메모리에서 강제로 읽어오게 함

 

 

  • synchronized 키워드가 무엇인가요?

synchronized 키워드는 Java에서 가장 기본적인 동기화 메커니즘을 제공

 

주요 특징 - 상호 배제, 가시성 보장, 재진입 가능

1. 상호배제(Mutual Exclusion): 한 번에 하나의 스레드만 synchronized 블록이나 메서드에 진입 가능

2. 가시성 보장(Visibility): synchronized 블록에 진입할 때 캐시를 무효화하고, 나올 때 모든 변경사항을 메인 메모리에 기록하는 것

3. 재진입 가능(Reentrancy): 이미 락을 획득한 스레드는 같은 객체의 다른 synchronized 메서드나 블록에 진입할 수 있는 것

 

synchronized는 메서드 전체나 특정 블록에 적용할 수 있으며, 객체 단위로 락을 걸게 됨

 

 

  • synchronized의 문제점은 무엇이 있나요?

성능 저하와 데드락 가능성

 

1. 성능 저하: 락의 획득과 해제에 따른 오버헤드가 발생 

2. 데드락 가능성: 여러 락을 사용할 때 데드락이 발생할 수 있음

(여러 스레드가 서로 다른 락을 순차적으로 요청하는 경우. 스레드 A: 락1을 획득하고 락2를 기다림 / 스레드 B: 락2를 획득하고 락1을 기다릴 때, 두 스레드 모두 영원히 대기 상태에 빠짐)

3) 세밀한 제어의 어려움: 읽기 작업에 대해서도 배타적 락을 사용하기때문에 불필요한 락으로 인해 성능저하가 발생할 수 있음

4) 대기 중인 스레드의 제어 불가: 대기 중인 스레드를 깨우거나 타임아웃을 설정하는 등의 세밀한 제어가 어려움

5) 확장성 제한: 경쟁이 심한 상황에서는 성능이 크게 저하될 가능성이 있음

 

위와 같은 문제점들 때문에 Java 5부터는 java.util.concurrent 패키지를 통해 더 세밀하고 효율적인 동기화 메커니즘을 제공

(ReentrantLock, ReadWriteLock, Semaphore - 더 세밀하고 유연한 동기화 도구들이 제공)

 

 

  • synchronized는 어떻게 구현되어 있나요?

객체의 모니터를 이용한 락 메커니즘

 

synchronized는 Java 객체의 내부 락(또는 모니터)을 사용하여 구현

1) 모니터 진입: synchronized 블록에 진입할 때, 해당 객체의 모니터를 획득하려 시도

2) 락 획득: 모니터가 사용 가능하면 스레드가 락을 획득하고 블록 내부로 진입

3) 락 대기: 모니터가 다른 스레드에 의해 사용 중이면, 현재 스레드는 대기 상태로 진입

4) 모니터 해제: synchronized 블록을 벗어날 때 모니터를 해제

 

JVM 레벨에서는 모니터 진입과 해제를 위해 monitorenter와 monitorexit라는 특별한 바이트코드 명령을 사용

 

실제 코드로 확인해봤다

increment 메서드 안에 synchronized 블록 생성
컴파일된 바이트코드(class)를 javap 명령어를 통해 역 어셈블해봤다

 

javac명령어로 컴파일하고 javap로 역어셈해본 결과를 grep으로 뽑아봤다

monitor 키워드와 monitorexit 키워드가 바이트코드 내에 삽입된 것을 확인할 수 있었다

increment 전체

 

  • atomic하다는 것이 무슨 의미인가요?

어떤 연산이 중간에 끊기지 않고 완전히 수행되거나 전혀 수행되지 않음을 의미

즉, 다른 스레드의 간섭 없이 원자적으로 실행될 수 있다는 것

 

멀티스레드 환경에서 atomic 연산은 아래의 특징들을 갖고잇음

1. 불가분성: 연산이 중간에 중단되거나 부분적으로 실행되지 않음

2. 일관성: 다른 스레드에서 볼 때, 연산은 한 순간에 모두 실행되거나 전혀 실행되지 않은 것처럼 보임

3. 순서 보장: atomic 연산들 사이의 순서가 보장됨

 

Java에서는 int, long 등의 기본 타입에 대한 읽기/쓰기가 atomic하지만, 복합 연산(예: i++)은 atomic하지 않음

 

 

  • atomic 키워드가 무엇인가요?

Java에서는 atomic 키워드가 존재하지 않음

하지만 Atomic 타입은 멀티 스레드 환경에서 원자성을 보장하기 위한 개념

CAS(Compare-And-Swap) 알고리즘을 통해 non-blocking한 방식으로 동작

ex) AtomicInter, AtomicLong, AtomicIntegerArray...

 

java.util.concurrent.atomic 패키지를 통해 atomic 연산을 제공하는 다양한 클래스들을 사용할 수 있음

 

 

  • CAS 알고리즘에 대해 설명해 주세요.

CAS 알고리즘은 현재 스레드가 가지고 있는 기존값과 메모리가 가지고 있는 값을 비교해

같은 경우 변경할 값을 메모리에 반영하고 true를 반환하고, 값이 다른 경우에는 변경값이 반영되지 않고 false를 반환한 다음 재시도를 하는 방식으로 동작

CAS 알고리즘을 통해 가시성과 원자성 문제를 해결할 수 있음

 

a) 장점:

  • 락보다 성능이 우수
  • 데드락이 발생하지 않음
  • 우선순위 역전 문제가 없

b) 단점:

  • ABA 문제가 발생할 수 있
  • 지속적인 실패 시 CPU 사용량이 증가할 수 있음

 

  • Vector, Hashtable, Collections.SynchronizedXXX의 문제점은 무엇인가요?

Collections 패키지의 synchronized 메서드

a) 과도한 동기화

  • 모든 메서드에 synchronized 키워드를 사용하여 전체 객체에 락을 검
  • 읽기 연산에도 락을 사용하여 불필요한 경합이 발생

b) 성능 저하

  • 락의 획득과 해제에 따른 오버헤드가 큼
  • 동시성이 높은 환경에서 심각한 성능 저하가 발생할 수 있음

c) 확장성 제한

  • 스레드 수가 증가할수록 성능이 급격히 저하

d) 세밀한 락 제어 불가

  • 컬렉션 전체에 대해 하나의 락만 사용하므로 세밀한 동시성 제어가 어려움

e) 동시 읽기 연산의 비효율성

  • 여러 스레드가 동시에 읽기만 하는 상황에서도 하나의 스레드만 접근 가능

=> Java 5부터는 ConcurrentHashMap, CopyOnWriteArrayList 등의 더 효율적인 동시성 컬렉션을 제공

 

 

  • SynchronizedList와 CopyOnArrayList의 차이를 설명해 주세요.

같은점: 둘 다 스레드 안전한 List 구현

차이점

- SynchronizedList: 내부적으로 리스트에 대한 모든 접근을 synchronized 키워드를 사용해 동기화(읽기/쓰기 모두 락을 요구하기 때문에 병렬 처리의 장점이 제한)

 

- CopyOnWriteArrayList: 쓰기 작업이 발생할 때마다 해당 블록에 잠금을 걸고 원본 배열의 요소를 복사하여 새로운 배열을 생성(읽기 작업이 동기화 없이 안전하게 수행), 주로 읽기 작업이 많고 쓰기 작업이 적은 환경에서 성능이 우수

 

 

  • ConcurrentHashMap의 동작 과정을 SynchronizedMap과 비교하여 설명해 주세요.

- SynchronizedMap

전체 맵에 대해 단일 락을 사용하므로, 동시성 성능이 좋지 않음

 

- ConcurrentHashMap

세그먼트(락을 분할) 기반으로 동작하여 여러 스레드가 동시에 접근 가능, 읽기 작업은 락 없이 수행 가능. 쓰기 작업은 세그먼트 단위로 락이 걸림

segment


 

추가 블로그(ThreadLocal)

https://yeonbot.github.io/java/ThreadLocal/

 

ThreadLocal 이란 ?

Thread의 장단점 과 ThreadLocal에 대해서

yeonbot.github.io

 

320x100

'Course > JSCODE - 자바' 카테고리의 다른 글

5주차 스터디 노트  (0) 2024.09.12
3주차 스터디 노트  (2) 2024.08.29
2주차 스터디 노트  (0) 2024.08.22

댓글