우리는 Java가 구현 한 동시 운영이 최종적으로 CPU에 의해 완료되어야한다는 것을 알고 있습니다. 그 동안, 우리는 Java 소스 코드를 .class 파일로 컴파일 한 다음로드 한 다음 가상 머신 실행 엔진에 의해 실행 된 다음 조립 언어로 해석 한 다음 운영 체제 지침으로 변환 한 다음 1, 0으로 변환 한 후 CPU가 인식되고 실행됩니다.
Java의 동시성을 언급하면 Java의 일반적인 키워드를 생각할 수는 없습니다 : 휘발성 및 동기화. 다음 으로이 두 종료 단어에서 분석 할 것입니다.
휘발성의 기본 구현 원리
동기화의 구현 원리 및 응용 프로그램
휘발성 물질
휘발성에 대해 말하면, 면접관은 Java 인터뷰에서 가장 좋아하는 질문입니다. 우리가 그것을 볼 때, 우리가 가장 먼저 생각하는 것은 스레드 간의 가시성을 유지하는 것입니다. 가벼운 동기화되어 동기화 된 경우에도 동기화 될 수 있습니다.
휘발성의 역할 :
휘발성에 의해 수정 된 변수의 경우, Java 메모리 모델은 모든 스레드에서 보이는 변수 값이 일관되도록합니다.
휘발성 작동 방식 :
휘발성 변수를 정의하고 IT 값을 할당하며 도구를 사용하여 JIT 컴파일러에서 생성 한 어셈블리 지침을 얻을 수 있습니다. 우리는 휘발성 변수에 글을 쓸 때 추가 명령이있을 것임을 알게 될 것입니다.
잠금 접두사 명령은 두 가지를 멀티 코어 프로세서로 돌아갑니다.
hary 현재 프로세서 캐시 라인의 데이터를 메모리에 기록하십시오.
this이 쓰기 백 메모리 작동은 다른 CPU의 데이터 캐시 메모리 주소를 무효화합니다.
우리가 위의 두 가지 점을 알면, 휘발유 변수의 메커니즘을 이해하는 것은 어렵지 않습니다.
여러 프로세서에서 각 프로세서의 캐시가 일관되도록하기 위해 캐시 일관성 프로토콜이 구현됩니다. 각 프로세서는 버스에서 전파 된 데이터를 스니핑하여 캐시 된 값이 만료되었는지 확인합니다.
동기화
멀티 스레드 동시성을 생각할 때, 내가 가장 먼저 생각하는 것은 동기화됩니다. 동기화로 번역. 우리 모두는 그것이 헤비급 잠금 장치라는 것을 알고 있습니다. 메소드 또는 코드 블록에 사용하면 스레드 가이 잠금을 얻을 때 다른 스레드가 부유 상태로 떨어지고, 이는 자바의 수면 상태에 나타납니다. 우리는 스레드의 서스펜션 및 실행 시간이 운영 체제의 커널 상태로 전송되어야한다는 것을 알고 있습니다 (커널 상태에 해당하는 것은 사용자 상태입니다). 특히 CPU 리소스가 낭비 되므로이 헤비급 잠금 장치는 진실성이 있습니다!
그러나 Java SE 1.6 이후, Java Maintenance Team은 일련의 최적화를 수행했으며 (이러한 최적화는 하나씩 논의됩니다), "무겁다"는 것이 아니며 과거에 장점이있는 재진입 잠금 장치는 덜 유리 해졌습니다 (Reentrantlock).
다음 측면에서 동기화 된 것에 대해 이야기 해 봅시다.
동기화를 달성하기 위해 동기화 된 기본 사항
동기화 된 구현이 잠금되는 방법
포지티브 잠금, 가벼운 잠금 (스핀 잠금), 헤비급 잠금 장치
잠금 업그레이드
Java에서 원자 운영을 구현하는 방법
동기화를 달성하기 위해 동기화 된 기본 사항 :
Hashtable, StringBuilder 및 기타 장소와 같은 개발 또는 Java 소스 코드에서 동기화 된 것을 볼 수 있습니다. 두 가지 일반적인 방법이 있습니다.
ⅰ, 동기화 방법
동기화 메소드는 메소드 전에 동기화되면됩니다. 한 스레드가 실행되면 다른 스레드가 잠금을 풀 때까지 대기 중입니다. 방법의 사용은 일반적인 동기화 방법과 정적 방법의 경우 두 가지 유형으로 나눌 수 있습니다. 그것들의 차이점은 잠긴 물체가 다르다는 것입니다. 일반적인 방법의 잠긴 위치는 현재 객체이며 정적 메소드의 잠긴 위치는 현재 클래스의 클래스 객체입니다.
ⅱ, 동기화 메소드 블록
동기화 메소드 블록은 동기화 된 후 브래킷에 구성된 객체를 잠금합니다. 이 객체는 값과 변수 또는 객체 일 수 있습니다.
synchronized 구간이 어떻게 잠금되는지 :
JVM 사양에서는 JVM에서 동기화 된 구현 원리를 볼 수 있습니다. JVM은 모니터 객체를 입력하고 종료하는 것을 기반으로 동기화 방법 및 코드 블록의 동기화를 구현합니다. 코드 블록은 Monitorenter 및 MonitoreXit 명령을 사용하여 구현됩니다. 동기화 방법은 JVM 사양에 구체적으로 제공되지 않습니다. 그러나 나는 특정 원칙이 달라야한다고 생각합니다. Java 소스 코드를 클래스 파일로 컴파일하고 클래스 바이트 코드 파일에서 동기화 된 메소드를 표시하는 것 이상입니다. 이 메소드는 바이트 코드 엔진 이이 메소드를 실행할 때 동기화됩니다.
deplection 잠금 잠금 장치, 경량 잠금 (스핀 잠금), 헤비급 잠금 장치 :
자물쇠에 대해 이야기하기 전에 Java 객체 헤더와 Java 객체 헤더를 알아야합니다.
동기화 된 상태에서 사용되는 잠금은 Java 객체 헤더에 저장됩니다. Java 객체 헤더에는 32bit/64 비트 (운영 체제의 비트 수에 따라 다름) 길이 Markword는 객체의 해시 코드 및 잠금 정보를 저장합니다. Markword에는 각각 Lock 00, 01, 10, 11의 상태를 나타내는 2 비트 공간이 있습니다.
포지티브 잠금 : 포지티브 잠금을 편심 잠금이라고합니다. 이름에서 우리는 그것이 특정 스레드를 향한 경향이있는 잠금 장치임을 알 수 있습니다.
실제 개발에서, 우리는 다중 스레드 동시성, 대부분의 동기화 방법이 동일한 스레드에 의해 수행되며, 하나의 방법과 경쟁하는 여러 스레드의 확률은 상대적으로 낮으므로, 잠금의 반복 획득 및 해제는 많은 자원 폐기물을 유발할 것입니다. 따라서 스레드가 저렴한 비용으로 잠금을 얻기 위해 바이어스 잠금이 도입됩니다. 스레드가 동기화 블록에 액세스하고 잠금을 획득하면 바이어스 잠금의 스레드 ID가 객체 헤더 및 스레드의 스택 프레임의 잠금 레코드에 저장됩니다. 앞으로 스레드가 동기화 블록에 들어가서 종료하면 잠금 및 잠금 해제를 위해 CAS 작업을 수행 할 필요가 없습니다. 객체 헤더의 현재 Markword를 가리키는 바이어스 잠금이 있는지 확인하면되면 (Markword에는 현재 객체가 바이어스 잠금을 지원하는지 여부를 나타내는 바이어스 잠금 플래그 비트가 있습니다. JVM 매개 변수를 사용하여 바이어스 잠금을 설정할 수 있습니다).
바이어스 된 잠금 장치의 출시와 관련하여, 바이어스 된 잠금 장치는 경쟁이 존재할 때까지 잠금 장치를 릴리스하는 메커니즘을 사용하므로 바이어스 잠금을 고정하는 스레드는 다른 스레드가 바이어스 잠금 장치를 놓고 경쟁하려고 할 때 잠금을 해제합니다.
참고 : Java6, 7에서는 기본적으로 바이어스 잠금이 시작됩니다.
경량 잠금 :
가벼운 잠금은 동기화 블록을 실행하기 전에 JVM이 현재 스레드의 스택 프레임에 잠금 레코드를 저장하고 객체 헤더의 Markword를 그 안에 복사 할 공간을 생성한다는 것입니다. 그런 다음 스레드는 객체 헤더의 Markword를 잠금 레코드에 대한 포인터로 바꾸려고합니다. 성공하면 현재 스레드가 잠금을 얻습니다. 실패하면 다른 스레드가 잠금을 놓고 경쟁하고 현재 스레드가 회전하여 잠금을 얻습니다.
④ 잠금 업그레이드 :
현재 스레드가 잠금을 얻기 위해 위의 방법을 시도 할 수없는 경우, 현재 잠금 장치가 경쟁 중이며 잠금 장치가 헤비급 잠금으로 업그레이드됨을 의미합니다.
경량 잠금 장치와 바이어스 잠금의 차이 :
경량 잠금 장치는 CAS 작업을 사용하여 경쟁없이 동기화에 사용되는 뮤트를 제거하는 반면, 바이어스 잠금은 경쟁없이 경쟁없이 전체 동기화를 제거하며 CAS 운영조차 수행되지 않습니다!
Java에서 원자 운영을 구현하는 방법 :
Java가 원자 운영을 구현하는 방법을 이해하기 전에 프로세서가 원자 운영을 구현하는 방법을 알아야합니다.
프로세서는 일반적으로 원자력 작업을 수행하는 두 가지 방법으로 나뉩니다. 캐시 잠금 및 버스 잠금 장치는 캐시 잠금이 더 좋으며 버스 잠금은 더 많은 리소스 소비입니다. (여기서 두 개의 잠금 방법에 대해 너무 많이 설명하지는 않지만 운영 체제에 자세한 설명이있을 것입니다).
Java는 (주로) 루프 CA를 사용하여 원자 운영을 구현하지만 CAS를 사용하여 원자 운영을 구현합니다. 또한 다음과 같은 전형적인 문제가 발생합니다.
1) ABA 문제
AtomicStampedReference 클래스는 JDK로 제공됩니다 (검사 예상 참조 및 예상 플래그 제공)
2) 긴 사이클 시간과 높은 오버 헤드
이것을 해결할 수 없습니다. 이것은 순환의 일반적인 문제입니다.
3) 공유 변수의 원자 연산 만 보장 할 수 있습니다.
JDK에서 Atomicreference가 제공되어 문제를 해결하여 CAS 운영을위한 클래스에 여러 공유 변수를 배치합니다.