최근에 저는 일부 사람들이 내장 잠금이라고 부르는 JAVA의 주류 잠금에 대한 개요를 작성하고 있습니다. 도움이 필요하시면 "메인스트림 잠금" 키워드로 댓글을 달면 제 마인드맵을 받아보실 수 있습니다.
다음으로, 다양한 유형의 잠금 장치에 대한 다양한 차원에서 간략하게 소개하고, 교환할 메시지를 남길 수 있도록 자세히 안내해 드리겠습니다.
I. 스레드가 동기화된 리소스를 잠궈야 하는가?
스레드가 동기화된 리소스를 잠글지 여부는 일반적으로 동시성 전략이라고 하며, 스레드 동기화 리소스를 다룰 때 주로 가정과 작동 방식에서 차이가 있습니다.
, 스레드는 동기 리소스를 잠글 필요가 있습니다: 비관적 잠금
- 가정: 비관적 잠금은 동시성 제어에 대해 보수적이거나 비관적인 접근 방식을 취합니다. 멀티스레드 환경에서는 공유 리소스에 액세스할 때마다 데이터 충돌 또는 데이터 불일치가 발생할 수 있으므로 리소스에 액세스하기 전에 잠금을 획득해야 한다고 가정합니다.
- 작동 모드: 스레드가 공유 자원에 액세스하려는 경우 먼저 잠금을 획득하려고 시도하고, 다른 스레드가 이미 잠금을 보유하고 있는 경우 잠금이 해제될 때까지 스레드가 차단되며, 일단 스레드가 잠금을 획득하면 자원에 독점적으로 액세스할 수 있으며, 그 동안 잠금을 획득하려는 다른 스레드는 차단됨, 자바에서는 동기화된 키워드와 ReentrantLock 등은 비관적 잠금의 구현입니다.
- 장점: 잠글 리소스에 액세스하기 직전에 비관적으로 잠그면 데이터의 일관성과 무결성을 보장하여 데이터 오류로 인한 동시 수정을 방지할 수 있습니다.
- 단점: 각 액세스에 대해 잠금을 획득해야 하므로 특히 동시성이 높은 시나리오에서 많은 컨텍스트 전환과 스레드 차단이 발생하여 시스템 성능과 처리량에 심각한 영향을 미칠 수 있습니다.
, 스레드는 동기 리소스를 잠글 필요가 없습니다: 낙관적 잠금
- 가정: 반면 낙관적 잠금은 대부분의 시나리오에서 여러 스레드가 공유 리소스에 동시에 액세스해도 데이터 충돌이 발생하지 않으므로 리소스에 액세스하기 전에 잠그지 않는다고 가정하여 낙관적 태도를 취합니다.
- 연산: 낙관적 잠금은 버전 번호 또는 CAS와 같은 메커니즘을 통해 구현되며, 스레드가 공유 자원을 업데이트하려고 할 때 먼저 자원의 버전 번호 또는 현재 값이 읽은 것과 일치하는지 확인하고, 일치하면 업데이트하고 버전 번호를 높이고, 일치하지 않으면 그 사이에 다른 스레드가 이미 자원을 수정했음을 의미하므로 스레드의 작업이 실패하고 일반적으로 작업을 다시 시도하거나 롤백하는 것을 선택하게 됩니다. 는 재시도 또는 롤백을 선택합니다.
- 이점: 낙관적 잠금은 잠금 사용량과 컨텍스트 전환을 줄여 대부분의 충돌이 없는 상황에서 시스템의 동시성 성능을 향상시킬 수 있습니다.
- 단점: 낙관적 잠금은 매우 동시적이고 자주 충돌하는 시나리오에서 많은 수의 재시도 및 롤백 작업으로 이어질 수 있으며, 이는 결국 성능에 영향을 줍니다. 또한 낙관적 잠금은 비관적 잠금에 비해 가시선이 더 복잡하며 추가 버전 번호 또는 CAS와 같은 지원이 필요합니다.
요컨대, 비관적 잠금 또는 낙관적 잠금을 사용할지 여부는 특정 비즈니스 시나리오와 성능 요구 사항에 따라 달라지며, 데이터 충돌 가능성이 높고 데이터 일관성 요구 사항이 엄격한 경우 비관적 잠금을 선택할 수 있고, 데이터 충돌이 적고 고성능 동시성 시나리오를 추구하는 경우 낙관적 잠금을 선택하는 것이 더 나은 선택입니다.
둘째, 동기 리소스 잠금이 실패할 때 스레드가 차단되는 경우
스레드가 동기화된 리소스를 얻으려는 시도에 실패했을 때 스레드를 처리하는 두 가지 전략이 있습니다:
잠금, 차단
스레드가 동기화 리소스 획득 시도에 실패하면 운영 체제에 의해 스레드가 중단되고 차단 전략이 선택된 경우 대기 대기열에 배치됩니다. 스레드는 동기화 리소스가 해제되고 잠금이 다시 획득될 때까지 실행을 중지하고 CPU 리소스를 해제합니다. 이 경우 스레드는 CPU 리소스를 지속적으로 소비하지 않지만 스레드 컨텍스트 전환의 오버헤드가 발생하여 동시성이 높은 시나리오에서 시스템 성능에 영향을 미칠 수 있습니다.
, 막힘 없음
비차단 시나리오에서는 스레드가 동기화된 리소스를 획득하려는 시도가 실패하면 대기 상태가 되는 대신 스핀이라는 프로세스에서 특정 코드(일반적으로 루프)를 계속 실행하며, 스핀 잠금은 이 전략의 구체적인 구현입니다.
스핀 잠금
스핀 잠금의 기본 개념은 스레드가 잠금 획득에 실패하면 즉시 차단 상태로 전환하는 대신 계속 반복하여 잠금이 해제되었는지 확인하는 것입니다. 이 접근 방식은 많은 경우 잠금 유지 시간이 매우 짧고 스핀 대기가 스레드 차단 및 깨우기보다 빠를 수 있으므로 스레드 컨텍스트 전환의 오버헤드를 피할 수 있습니다.
적응형 스핀 잠금
적응형 스핀 락은 기본 스핀 락의 최적화로, 시스템의 작동 조건과 과거 데이터에 따라 스핀 시도 횟수를 동적으로 조정하며, 시스템이 락의 유지 시간이 짧거나 현재 CPU 부하가 낮다고 감지하면 스레드가 더 많은 스핀을 시도할 수 있고, 반대로 락의 유지 시간이 길거나 시스템이 높은 부하 상태인 경우 스레드는 더 적은 스핀 후 차단 상태로 들어갈 수 있습니다. 불필요한 CPU 소비를 줄이고 전반적인 시스템 효율성을 개선하기 위해 시도합니다.
이 두 가지 전략에는 고유 한 장단점이 있으며 차단 전략은 CPU 리소스를 절약 할 수 있지만 높은 동시성 및 잠금 경쟁 시나리오에서는 빈번한 스레드 컨텍스트 전환이 성능 병목 현상이 될 수 있으며 비 차단 스핀 잠금 및 적응 형 스핀 잠금은 잠금 유지 시간이 짧은 경우 효율성을 향상시킬 수 있지만 과도한 스핀은 CPU 유휴 및 자원 낭비로 이어질 수 있으므로 실제 사용 시스템 특성 및 응용 시나리오에 따라 적절한 잠금 전략을 선택해야 함. 따라서 구체적으로 선택해야합니다. 따라서 실제로는 특정 시스템 특성 및 적용 시나리오에 따라 적절한 잠금 전략을 선택해야 합니다.
셋째, 동기화된 리소스를 놓고 경쟁하는 여러 스레드의 프로세스 세부 정보가 차별화됩니다.
잠금 없음
- 잠금 없는 경우, 시스템은 공유 리소스를 보호하기 위해 명시적인 잠금을 적용하지 않으며, 스레드는 리소스를 수정할 때 데이터 일관성을 보장하기 위해 원자 연산 또는 CAS와 같은 기술을 사용합니다.
- 여러 스레드가 동시에 리소스를 수정하려고 하면 한 스레드의 수정만 성공하고 다른 스레드의 수정은 실패하여 다시 시도해야 합니다. 이 재시도 메커니즘은 일반적으로 한 스레드의 수정이 성공할 때까지 루프와 CAS 연산을 통해 구현됩니다.
- 장점: 잠금 없는 프로그래밍은 잠금으로 인한 컨텍스트 전환 및 차단 대기를 방지하고 시스템의 동시성 성능을 향상시킬 수 있습니다.
- 단점: 잠금 없는 프로그래밍은 구현하기가 더 복잡하고 경쟁이 치열한 시나리오에서는 많은 수의 재시도 작업과 CPU 캐시 무효화로 이어질 수 있으며, 이는 결국 성능에 영향을 미칩니다.
편향 잠금
- 바이어스 잠금은 하나의 스레드만 동기화된 리소스에 자주 액세스하는 상황을 처리하기 위한 Java 가상 머신의 최적화 전략입니다.
- 스레드가 처음으로 잠금을 획득하면 잠금은 해당 스레드에 유리하게 편향됩니다. 이후 해당 스레드가 동일한 동기화된 리소스에 다시 액세스할 때 추가 동기화 작업을 수행할 필요가 없으며 리소스에 직접 액세스할 수 있습니다.
- 다른 스레드가 편향된 잠금을 획득하려고 하면 편향된 잠금이 경량 또는 중량 잠금으로 업그레이드되어 다중 스레드 경합의 경우를 처리합니다.
- 장점: 바이어스드 락은 잠금을 획득하고 해제하는 스레드의 오버헤드를 줄여 단일 스레드 환경에서 프로그램의 실행 효율을 향상시킵니다.
- 단점: 바이어스 잠금은 멀티스레드 경쟁 환경에서 추가적인 잠금 해제 및 업그레이드 오버헤드를 추가할 수 있습니다.
경량 잠금
- 경량 잠금은 여러 스레드가 동기화된 리소스를 놓고 경쟁하지만 잠금이 짧은 시간 동안 유지되는 상황을 처리하는 데 사용됩니다.
- 스레드가 경량 잠금을 획득하려고 하면 자체 스레드 ID를 객체 헤더에 저장하고 객체 헤더의 상태를 잠금 해제에서 경량 잠금으로 변경하려고 시도합니다.
- 이 시점에서 다른 스레드도 동일한 잠금을 획득하려고 시도하지만 이미 잠금이 유지되고 있는 것을 발견하면 해당 스레드는 즉시 차단하지 않고 잠금이 해제되었는지 확인하기 위해 계속 반복하는 스핀 상태에 들어갑니다.
- 스핀 대기 후에도 잠금이 해제되지 않으면 경량 잠금이 헤비급 잠금으로 업그레이드되어 너무 많은 스레드가 스핀 상태에 갇혀 CPU 리소스를 낭비하는 것을 방지합니다.
- 장점: 경량 잠금은 스레드 차단 및 깨우기 오버헤드를 방지하고 잠금 경쟁이 치열하지 않을 때 시스템 동시성 성능을 향상시킬 수 있습니다.
- 단점: 긴 스핀 대기는 특히 잠금이 장시간 유지되거나 멀티스레드 경쟁이 심한 경우 CPU 유휴 상태와 에너지 낭비로 이어질 수 있습니다.
, 헤비급 잠금
- 헤비급 잠금은 여러 스레드가 동기화 리소스를 놓고 경쟁하여 잠금이 장시간 유지될 수 있는 상황을 처리하는 데 사용되는 상호 제외 잠금의 전통적인 구현입니다.
- 스레드가 헤비급 잠금을 획득하면 잠금을 획득하려는 다른 스레드는 즉시 차단되고 운영 체제의 대기 대기열에 배치됩니다.
- 잠금이 해제되면 운영 체제는 대기 중인 스레드를 선택하여 잠금을 해제하고 잠금을 부여하면 해당 스레드가 실행을 계속할 수 있습니다.
- 장점: 헤비급 잠금은 데이터 일관성과 무결성을 보장하며, 동시성이 높고 잠금 경합이 심한 시나리오에 적합합니다.
- 단점: 헤비급 잠금을 획득하고 해제하려면 스레드를 차단하고 해제해야 하므로 컨텍스트 전환 오버헤드가 커지고 시스템의 동시성 성능이 저하됩니다.
여러 스레드가 잠금을 두고 경쟁할 때 대기열에 넣거나 대기열에 넣지 않기
공정 잠금
- 공정한 잠금 메커니즘에서는 여러 스레드가 동일한 잠금 리소스를 놓고 경쟁하는 경우 스레드가 잠금을 신청하는 순서대로 대기열에 대기해야 합니다.
- 스레드가 잠금을 요청할 때 다른 스레드가 이미 잠금을 보유하고 있는 경우 해당 스레드는 선착순 순서대로 대기 대기열에 들어갑니다.
- 잠금이 해제되면 잠금 관리자는 대기 대기열에서 가장 오래 기다린 스레드를 선택하여 잠금을 부여함으로써 각 대기 스레드가 공정한 기회를 얻어 잠금을 획득할 수 있도록 합니다.
- 장점: 공정한 잠금은 '굶주림' 문제를 방지합니다. 즉, 오랫동안 기다린 스레드가 결국 잠금을 얻을 수 있으므로 모든 스레드에 공정성을 보장합니다.
- 단점: 대기 대기열을 유지하고 잠금을 획득하고 해제할 때마다 대기 스레드를 확인해야 하므로 공정 잠금의 성능이 비공정 잠금에 비해 약간 떨어질 수 있습니다.
비공정 잠금
- 비공정 잠금은 여러 스레드가 잠금 리소스를 놓고 경쟁할 때 새로 도착한 스레드가 대기 대기열에 관계없이 잠금을 획득하려고 시도하는, 즉 '대기열 점프'를 허용합니다.
- 새로 도착한 스레드가 잠금 획득에 성공하면 이미 대기 대기열에 있는 스레드를 기다릴 필요 없이 즉시 중요 영역에서 코드를 실행할 수 있습니다.
- 새로 도착한 스레드가 잠금 획득 시도에 실패하면 그 때에만 잠금 해제 대기 대기열에 배치됩니다.
- 이점: 비공정 잠금은 스레드가 기다릴 필요 없이 즉시 잠금을 획득하여 스레드 컨텍스트 스위치의 오버헤드를 줄일 수 있으므로 일부 시나리오에서 더 높은 동시성 성능을 제공할 수 있습니다.
- 단점: 비공정 잠금은 대기 시간이 긴 일부 스레드가 오랜 시간 동안 잠금을 얻지 못하는 '스타 기아'를 초래할 수 있습니다. 또한 큐 점핑 동작으로 인해 비공정 잠금의 스레드 스케줄링 불확실성이 커져 시스템의 전반적인 안정성에 영향을 미칠 수 있습니다.
요컨대, 공정 잠금과 비공정 잠금은 각각의 장단점이 있으며 다양한 애플리케이션 시나리오에 적합합니다. 공정 잠금은 스레드 간의 공정성과 기아 문제를 방지하는 데 더 많은 주의를 기울이며 높은 시스템 안정성이 필요한 시나리오에 적합하고, 비공정 잠금은 동시성 성능에 더 많은 주의를 기울이며 잠금에 치열한 경쟁이 없거나 응답 시간에 대한 요구가 높은 시나리오에 적합합니다. 실제 사용 시에는 특정 비즈니스 요구사항과 성능 지표에 따라 적합한 잠금 유형을 선택해야 합니다.
V. 스레드 내 여러 프로세스가 동일한 잠금을 획득할 수 있나요?
재입력 잠금 가능
- 재진입 잠금을 사용하면 동일한 스레드가 잠금을 보유한 상태에서 잠금을 다시 요청하고 획득할 수 있습니다.
- 스레드가 잠금을 획득한 후 실행 중에 해당 잠금이 필요한 다른 코드 블록을 입력해야 하는 경우, 재진입 잠금이 사용되므로 스레드는 차단되지 않고 잠금을 다시 성공적으로 획득할 수 있습니다.
- 재진입 잠금에서 각 잠금은 스레드가 잠금을 획득할 때마다 1을 더하고 스레드가 잠금을 해제할 때 1을 빼는 카운터와 연결됩니다. 카운터가 0일 때만 실제로 다른 대기 중인 스레드에 잠금이 해제됩니다.
- 장점: 재진입자 잠금은 재귀 호출과 복잡한 다단계 동기화 구조를 지원하고 교착 상태를 방지하며 코드를 더 간결하고 이해하기 쉽게 만듭니다.
- 단점: 재진입 잠금의 구현은 비재진입 잠금에 비해 약간 더 복잡할 수 있습니다.
재진입 불가 잠금
- 재진입 금지 잠금은 동일한 스레드가 잠금을 보유한 상태에서 다시 잠금을 요청하고 획득하는 것을 허용하지 않습니다.
- 스레드가 잠금을 획득한 후 실행 중에 해당 잠금이 필요한 다른 코드 블록에 진입해야 하는 경우, 재진입 불가능한 잠금이 사용 중이므로 잠금을 획득할 수 없어 스레드가 차단되어 교착 상태가 발생하거나 스레드가 실행을 계속할 수 없게 됩니다.
- 비재진입 잠금에서는 스레드가 잠금을 획득하면 해당 스레드가 잠금을 해제할 때까지 잠금을 요청하는 다른 모든 스레드가 차단됩니다.
- 장점: 재진입 금지 잠금을 비교적 간단하게 구현할 수 있습니다.
- 단점: 비재진입 잠금은 재귀 호출과 복잡한 다계층 동기화 구조를 지원하지 않아 교착 상태가 쉽게 발생할 수 있으며, 경우에 따라 스레드가 실행을 계속할 수 없어 시스템의 동시성과 안정성이 저하될 수 있습니다.
실제로 대부분의 프로그래밍 언어와 프레임워크 패턴은 복잡한 동기화 시나리오를 더 잘 처리하고 교착 상태 문제를 피하기 위해 재진입 잠금을 사용합니다. 비재진입 잠금은 일반적으로 특정 저수준 동기화 작업이나 극한의 성능 요구 사항이 있는 시나리오에 사용됩니다.
여섯. 여러 스레드가 잠금을 공유할 수 있나요?
공유 잠금
- 공유 잠금을 사용하면 여러 스레드가 동시에 동일한 잠금을 획득하고 유지할 수 있으며, 주로 공유 데이터를 읽는 시나리오에서 사용됩니다.
- 스레드가 공유 잠금을 획득하면 다른 스레드도 읽기 작업에 대한 잠금을 획득할 수 있지만, 독점 잠금이 필요한 쓰기 작업에는 잠금을 획득할 수 없습니다.
- 공유 잠금 메커니즘을 사용하면 여러 스레드가 서로를 차단하지 않고 공유 리소스를 동시에 읽을 수 있어 시스템의 동시 성능과 리소스 활용도가 향상됩니다.
- 장점: 공유 잠금은 읽기는 많고 쓰기는 적은 시나리오에 적합하며 시스템 병렬 처리와 효율성을 향상시킬 수 있습니다.
- 단점: 공유 잠금은 동시 읽기를 허용하므로 쓰기 작업이 있을 때 데이터 일관성을 보장하기 위해 추가적인 메커니즘이 필요합니다.
제외 잠금
독점 잠금이라고도 하는 배타적 잠금은 두 개 이상의 스레드가 동시에 동일한 잠금을 획득하고 보유하는 것을 허용하지 않습니다. 한 스레드가 독점 잠금을 획득하면 잠금을 보유한 스레드가 잠금을 해제할 때까지 잠금을 획득하려는 다른 스레드가 차단됩니다.
제외 잠금은 주로 중요한 영역의 코드를 보호하는 데 사용되며, 특정 순간에 하나의 스레드만 공유 리소스를 수정할 수 있도록 하여 데이터 일관성과 무결성을 보장합니다.
독점 잠금 메커니즘에서는 한 스레드가 쓰기 작업에 대한 잠금을 획득하면 다른 모든 스레드는 잠금을 획득할 수 없으며 잠금이 해제될 때까지 기다려야 합니다.
장점: 제외 잠금은 데이터 충돌과 불일치를 간단하고 효과적으로 방지할 수 있으며 데이터 무결성 및 일관성에 대한 요구 사항이 높은 시나리오에 적합합니다.
단점: 독점 잠금으로 인해 스레드 차단 및 컨텍스트 전환 오버헤드가 발생하여 시스템의 동시성 성능이 저하될 수 있습니다.
실제로는 특정 비즈니스 요구 사항과 시나리오에 따라 공유 잠금, 전용 잠금 또는 이 두 가지를 조합하여 스레드 간 동기화 및 데이터 보호를 달성하도록 선택할 수 있습니다. 공유 잠금은 읽기 집약적인 시나리오에서 자주 사용되는 반면, 전용 잠금은 데이터를 쓰거나 수정하는 데 더 적합합니다. 잠금 메커니즘을 합리적으로 선택하고 사용함으로써 시스템의 동시성 성능과 데이터 보안 간의 균형을 맞출 수 있습니다.




