Week4 Java Concurrency Programming
동시성 프로그래밍
동시성과 병렬성의 차이점이 뭔가요?
자바에서는 여러 스레드가 시분할로 실행되는 것을 동시성이라고 하고, 멀티코어 CPU에서 여러 스레드를 실제로 동시에 실행하는 것을 병렬성이라고 합니다.
동시성:
여러 작업이 논리적으로 동시에 진행되는 것. 여러 작업이 겹쳐서 진행되는 것처럼 보이지만, 실제로는 하나의 프로세서에서 시분할 방식으로 순차적으로 작업이 처리됩니다.
병렬성:
여러 작업이 물리적으로 동시에 실행되는 것. 다수의 프로세서가 각각의 작업을 병렬로 실행할때 발생합니다.
Thread-safe하다는게 무슨 뜻인가요?
Thread-safe하다는 것은 여러 스레드가 동시에 특정 코드나 객체에 접근하더라도 그 코드나 객체의 상태가 일관성을 유지하며, 예상치 못한 동작이나 에러가 발생하지 않음을 의미합니다. 동기화, 불변객체, 스레드 전용 객체등의 기법으로 구현할 수 있습니다.
Thread-safe하지 않은 환경에서는 data race가 발생할 수 있습니다. 데이터 레이스는 여러 스레드가 동시에 공유된 자원에 접근하여 읽거나 쓸 때 발생할 수 있는 문제로, 스레드 간의 실행 순서가 불확정적이기 때문에, 한 스레드의 작업중 다른 스레드에 의해 값이 변조될 수 있습니다.
가시성 문제와 원자성 문제에 대해 설명해주세요.
가시성 문제는 여러 스레드가 같은 변수를 공유할 때, 한 스레드에서 변경한 값이 다른 스레드에게 즉시 보이지 않을 수 있는 문제입니다. 이는 스레드가 변수 값을 CPU 캐시에 저장하고, 메인 메모리와의 동기화가 제대로 이루어지지 않을 때 발생합니다. 자바에서 volatile/synchronized 키워드를 사용하여 일관성을 유지하여 가시성 문제를 해결할 수 있습니다.
원자성 문제는 여러 스레드가 하나의 복합 작업을 동시에 수행할 때 발생할 수 있습니다. 예를 들어 count++ 연산은 총 3단계로 이루어집니다. 이 과정에서 다른 스레드가 개입하면 잘못된 결과가 나올 수 있습니다. 이를 해결하기 위해서는 AtomicInteger와 같은 Atomic 클래스를 사용할 수 있습니다. (내부적으로 CAS 알고리즘을 사용)
가시성 문제에 대해 조금 더 자세히 설명해 주세요. 여러 스레드가 모두 한 CPU의 캐시 메모리를 읽으면 가시성 문제가 발생하지 않을 것 같은데, 어떻게 생각하시나요?
가시성 문제는 여러 스레드가 같은 데이터를 읽고 쓸 때, 한 스레드의 변경 사항이 다른 스레드에게 즉시 보이지 않을 수 있는 문제를 말합니다. 이는 주로 CPU 캐시와 메인 메모리 간의 불일치 때문에 발생합니다.
각 스레드마다 CPU캐시 메모리를 별도로 들고 있으므로 CPU 캐시 메모리 값이 항상 같을 수 없고, 동기화가 맞춰지기 전까지는 가시성 문제가 발생합니다.
다만, 스레드가 동일한 CPU소켓의 코어에서 실행되면 모든 스레드가 L3 캐시를 바라볼 수 있습니다. 항상 동일 CPU의 캐시 메모리를 읽고, 캐시미스가 발생하여 메인 메모리 값을 읽지 않는 상황에서는 가시성 문제가 발생하지 않을것 같습니다. 다만, 컴파일러가 성능 최적화를 위해 명령어 재정렬을 하는 경우에 코드 실행 순서가 변경되어 가시성 문제가 발생할 수 있습니다.
자바의 동시성 이슈를 해결하는 방법을 아는 만큼 설명해주세요.
자바에서 동시성 문제는 주로 두 가지 측면에서 발생합니다. 가시성 문제, 원자성 문제
자바에서 동시성 이슈를 해결하는 방법은 여러가지 있습니다.
기본적으로 Synchronized 키워드를 사용하여 코드 블록을 동기화하여, 하나의 스레드만 접근하도록 할 수 있는 방법이 있습니다. 다만, 병목 현상으로 인해 성능 저하가 생길 수 있습니다.
두번째로는 불변 객체를 사용하는 것입니다. 상태 변경을 할 수 없기때문에, 여러 스레드가 접근하여도 안전합니다.
세번쨰로는 Volatile을 사용할 수 있습니다. 항상 메인 메모리에서 읽고 쓰도록 할 수 있습니다. Volatile에는 명령어 재정렬이 발생하지 않습니다.
네번째, AtomicInteger 같은 CAS(Compare - and - swap)알고리즘을 사용해 원자성을 보장할 수 있습니다
마지막으로 Concurrent 패키지를 사용하여 스레드 안전한 컬렉션을 사용할 수 있습니다.
Synchronized 키워드가 뭔가요?
자바에서 데이터 레이스가 발생하지 않도록 동시성 문제를 해결하기 위한 도구입니다. 하나의 스레드만 접근할 수 있도록 보장하고, 다른 스레드들은 해당 메서드나 블록이 모니터 락을 해제할 때까지 대기 상태에 들어가게 됩니다.
특징:
상호배제: 한번에 하나씩
가시성 보장: 메인 메모리에 기록
재진입 가능: Synchronized된 A메서드안에 다른 Synchronized된 B메서드가 있는 상황에서, 재귀적으로 메서드 호출시 새로운 락을 요구하게되면 데드락 발생. Reentrant락을 통해 동일한 락을 다시 획득 안해도된다.
모니터 락 과정:
자바에서
synchronized
블록이나 메서드에 접근하려는 스레드는 락을 얻지 못할 경우 JVM과 운영체제에 의해 대기 상태로 전환됩니다.이 대기 상태는
while
루프와 같은 방식으로 CPU를 지속적으로 사용하면서 확인하는 것이 아니라, 운영체제가 스레드를 일시 정지시킵니다.락을 소유한 스레드가
synchronized
블록을 빠져나오면, JVM은 모니터 대기 큐에서 대기 중인 스레드 중 하나를 선택하여 락을 할당합니다.
Synchronized의 문제점
synchronized
의 주요 문제점 중 하나는 병목 현상으로 인한 성능 저하입니다. synchronized
블록이나 메서드에 하나의 스레드만 접근할 수 있기 때문에, 다른 스레드들은 대기 상태에 들어가야 하며, 이로 인해 순차 실행이 이루어지면서 시스템의 처리량이 감소할 수 있습니다.
또한, 데드락의 위험이 있습니다. 여러 스레드가 서로의 락을 기다리면서 무한 대기 상태에 빠질 수 있으며, 이는 프로그램이 멈추는 원인이 됩니다.
공정성 문제도 발생할 수 있는데, 대기 중인 스레드들 중에서 어떤 스레드가 락을 먼저 획득할지에 대한 보장이 없기 때문에 특정 스레드가 지속적으로 락을 얻지 못하는 기아(Starvation) 상황이 발생할 수 있습니다.
Synchronized는 어떻게 구현되어 있나요?
Synchronized는 자바의 JVM 수준에서 구현된 동기화 메커니즘입니다. JVM은 모니터를 활용하여 동기화를 처리합니다.
모든 객체는 암묵적으로 모니터를 가지고 있고, 스레드가 synchronized 블록이나 메서드에 진입하려고 하면, JVM은 해당 객체의 모니터 락을 획득하려고 시도합니다. 다른 스레드가 이미 가지고 있다면, 대기 상태로 전환됩니다.
JVM은
synchronized
블록의 시작 부분에서 Monitor Enter 명령어를 실행하고, 종료 부분에서 Monitor Exit 명령어를 실행합니다.Monitor Enter는 스레드가 모니터 락을 획득하려고 시도하며, 성공하면 스레드는 블록 내부의 코드를 실행할 수 있습니다.
Monitor Exit는 스레드가 모니터 락을 해제하고, 다른 대기 중인 스레드가 락을 획득할 수 있도록 합니다.'
atomic하다는 것이 무슨 의미인가요?
원자성(atomicity)는 특정 작업이 더 이상 나눌 수 없는 단위로 수행된다는 것을 의미합니다. 하나의 작업이 완전히 실행되거나 전혀 실행되지 않는 상태를 의미합니다. 이는 작업이 도중에 중단되거나 다른 작업이 끼어들 수 없는 것을 보장합니다.
데이터베이스에서의 원자성: 모두 실행되거나 아무것도 실행되지 않는 특성
동시성 프로그래밍에서의 원자성: 특정 작업이 여러 단계로 이루어져 있어도, 하나의 단위 작업처럼 실행되어 다른 스레드가 간섭할 수 없는 것.
CAS 알고리즘에 대해 설명해주세요
java.util.concurret 패키지에서 제공하는 atomic 클래스들이 내부적으로 사용하는 알고리즘입니다. 동기화 없이 여러 스레드가 안전하게 공유 데이터를 수정할 수 있도록 도와주는 알고리즘입니다.
메모리 주소, 예상되는 값, 새로운 값을 사용하여 동작합니다.
특정 메모리 위치에서 현재 값을 읽습니다. (예상되는 값. 스레드가 메모리 위치에서 값을 읽은 순간 존재하는 값)
업데이트를 위한 새로운 값을 연산
기존 값을 업데이트 하기전에 다시 한번 확인하고, 새로운 값으로 업데이트합니다.
Vector, HashTable, Collections.synchronizedXXX의 문제점이 뭔가요?
Vector, HashTable, Collections.synchronized 등은 모두 synchronized 키워드를 사용하여 동기화를 처리합니다. 이로 인해 전체 메서드에 락이 걸리게 되며, 하나의 스레드만 해당 메서드를 실행합니다. 전체 메서드에 대해 동기화를 적용하여, 동기화가 필요하지 않은 경우에도 동기화가 발생할 수 있습니다.
자바 5 이후부터는 ConcurrentHashMap과 같은 더 효율적인 대체 기술들이 등장하여, 기존 동기화 컬렉션들은 잘 사용되지 않습니다. 락 분할을 사용하여 특정 데이터가 저장된 버킷(슬롯)에만 락이 걸립니다.
SynchronizedList와 CopyOnWriteArrayList의 차이
Thread-safe한 리스트를 제공하기 위해 사용됩니다.
SyncrhoinzedList는 내부적으로 synchronized 블록이 사용됩니다.
CopyOnWriteArrayList는 쓰기 작업이 발생할 떄마다 전체 배열의 복사본을 생성합니다. 읽기 작업은 기존 배열을 참조하므로, 동기화 없이도 안전하게 동시 읽기 작업이 가능합니다. 쓰기 작업이 많아지면 메모리 사용량이 많아지는게 단점이라서 읽기 작업이 빈번하고 쓰기 작업은 적은 경우에 적합합니다.
ConcurrentHashMap의 동작 과정을 SynchronizedMap과 비교하여 설명해주세요
SynchronizedMap은 모든 작업에 대해 맵 전체에 단일 락을 사용하여 동기화를 보장합니다. 이로 인해 병목 현상과 성능 저하가 발생할 수 있으며, 특히 동시성 처리가 중요한 환경에서는 확장성이 제한적입니다.
반면, ConcurrentHashMap은 락 분할(Lock Stripping) 기법을 사용하여, 맵 전체가 아닌 개별 버킷에 대한 락을 걸어 동기화를 처리합니다. 이를 통해 여러 스레드가 동시에 다른 버킷에 접근할 수 있으며, 특히 읽기 작업이 비동기적으로 수행되므로 성능이 뛰어납니다. 이로 인해 동시성 처리 성능이 크게 향상되며, 확장성이 뛰어납니다.
Last updated