이 기사에서는 Java 메모리 모델, 휘발성에 대한 자세한 설명, 동기화 구현 원리 등 동시성 프로그래밍과 관련된 문제를 주로 정리하는 java에 대한 관련 지식을 제공합니다. 함께 살펴보시기 바랍니다. 모두를 돕습니다.
추천 학습: "java 비디오 튜토리얼"
Java 메모리 모델은 Java Memory Model 또는 줄여서 JMM입니다. JMM은 JVM(Java Virtual Machine)이 컴퓨터 메모리(RAM)에서 작동하는 방식을 정의합니다. JVM은 전체 컴퓨터 가상 모델이므로 JMM은 JVM과 제휴되어 있습니다. Java1.5 버전에서는 이를 리팩토링했으며 현재 Java는 여전히 Java1.5 버전을 사용합니다. Jmm이 직면한 문제는 현대 컴퓨터에서 직면한 문제와 유사합니다.
실제 컴퓨터의 동시성 문제. 실제 컴퓨터에서 발생하는 동시성 문제는 가상 컴퓨터의 상황과 많은 유사성을 가지고 있습니다. D "Google의 GOOGLE Plot에 있는 Jeff Dean의 보고서"에 따르면
다음 사례는 예시일 뿐이며 실제 상황을 나타내지는 않습니다.int형 데이터 1M을 메모리에서 읽어와 CPU가 쌓는다면 얼마나 걸릴까요?
간단히 계산해 보면 Java의 int 유형은 32비트, 4바이트입니다. CPU 계산 시간은 262144
0.6 = 157286나노초입니다. 우리는 메모리에서 1M 데이터를 읽는 데 250,000나노초가 걸린다는 것을 알고 있지만, 둘 사이에는 간격이 있지만(물론 이 간격은 작지 않으며, 100,000나노초는 CPU가 거의 200,000개의 명령을 실행하는 데 충분한 시간입니다). 여전히 같은 규모입니다. 그러나 캐싱 메커니즘이 없으면 각 숫자를 메모리에서 읽어야 함을 의미합니다. 이 경우 CPU가 메모리를 한 번 읽는 데 100나노초가 걸리고 262144개의 정수가 메모리에서 CPU로 읽혀집니다. 계산 시간은 262144100+250000 = 26464400나노초가 소요되며 이는 크기의 차이입니다.
추상적인 관점에서 JMM은 스레드와 메인 메모리 사이의 추상적인 관계를 정의합니다. 스레드 간의 공유 변수는 메인 메모리(메인 메모리)에 저장되며, 각 스레드에는 프라이빗이 있습니다. 스레드가 읽고 쓸 수 있는 공유 변수의 복사본을 저장하는 로컬 메모리(로컬 메모리). 로컬 메모리는 JMM의 추상적인 개념이며 실제로 존재하지 않습니다. 캐시, 쓰기 버퍼, 레지스터, 기타 하드웨어 및 컴파일러 최적화를 다룹니다.
가시성은 여러 스레드가 동일한 변수에 액세스할 때 한 스레드가 변수 값을 수정하면 다른 스레드가 수정된 값을 즉시 볼 수 있음을 의미합니다.
스레드에 의한 변수에 대한 모든 작업은 작업 메모리에서 수행되어야 하고 주 메모리에서 변수를 직접 읽고 쓸 수 없기 때문에 공유 변수 V의 경우 먼저 자체 작업 메모리에 있는 다음 주 메모리에 동기화됩니다. 그러나 시간이 지나면 메인 메모리로 플러시되지는 않지만 일정한 시간 차이가 있습니다. 분명히 현재로서는 변수 V에 대한 스레드 A의 작업이 더 이상 스레드 B에 표시되지 않습니다.
공유 객체 가시성 문제를 해결하기 위해 휘발성 키워드나 잠금을 사용할 수 있습니다.
Atomicity: 즉, 하나의 작업 또는 여러 작업이 모두 실행되고 실행 프로세스가 어떤 요인으로 인해 중단되지 않거나 전혀 실행되지 않습니다.
우리 모두는 CPU 리소스 할당이 스레드를 기반으로 하며 시간 공유 방식으로 호출된다는 것을 알고 있습니다. 운영 체제에서는 50밀리초와 같은 짧은 시간 동안 프로세스를 실행할 수 있습니다. 프로세스를 다시 선택합니다("작업 전환"이라고 함). 이 50밀리초를 "타임 슬라이스"라고 합니다. 대부분의 작업은 시간 세그먼트가 끝난 후 전환됩니다.
그렇다면 스레드 전환이 버그를 일으키는 이유는 무엇일까요?
운영 체제가 작업 전환을 수행하기 때문에 CPU 명령이 실행된 후에 이러한 일이 발생할 수 있습니다! 이는 고급 언어의 명령문이 아니라 CPU 명령어, CPU 명령어, CPU 명령어라는 점에 유의하십시오. 예를 들어 count++는 Java에서는 한 문장에 불과하지만 고급 언어에서는 명령문을 완료하려면 여러 CPU 명령이 필요한 경우가 많습니다. 실제로 count++에는 적어도 3개의 CPU 명령어가 포함되어 있습니다! 那么线程切换为什么会带来 bug 呢?
因为操作系统做任务切换,可以发生在任何一条 CPU 指令执行完!注意,是 CPU 指令,CPU 指令,CPU 指令,而不是高级语言里的一条语句。比如 count++,在 java 里就是一句话,但高级语言里一条语句往往需要多条 CPU 指令完成。其实 count++至少包含了三个 CPU 指令!
可以把对 volatile 变量的单个读/写
,看成是使用同一个锁对这些单个读/写
휘발성 변수의 단일 읽기/쓰기는 동일한 잠금을 사용하여 이러한 단일 코드를 읽고 쓰는 것으로 간주할 수 있습니다.3.1. 휘발성 기능
public class Volati { // 使用volatile 声明一个64位的long型变量 volatile long i = 0L;// 单个volatile 变量的读 public long getI() { return i; }// 单个volatile 变量的写 public void setI(long i) { this.i = i; }// 复合(多个)volatile 变量的 读/写 public void iCount(){ i ++; }}
public class VolaLikeSyn { // 使用 long 型变量 long i = 0L; public synchronized long getI() { return i; }// 对单个的普通变量的读用同一个锁同步 public synchronized void setI(long i) { this.i = i; }// 普通方法调用 public void iCount(){ long temp = getI(); // 调用已同步的读方法 temp = temp + 1L; // 普通写操作 setI(temp); // 调用已同步的写方法 }}따라서 휘발성 변수 자체는 다음과 같은 특성을 갖습니다.
동시에 이 명령어는 현재 프로세서 캐시 라인의 데이터를 시스템 메모리에 직접 기록하며, 이 쓰기 작업은 다른 CPU의 이 주소에 캐시된 데이터를 무효화합니다.
Synchronized에서 사용하는 잠금은 Java 객체 헤더에 저장됩니다. Java 객체의 객체 헤더는 마크 워드와 클래스 포인터의 두 부분으로 구성됩니다.
객체의 마크 워드에 잠금 정보가 존재합니다. MarkWord의 기본 데이터는 객체의 HashCode 및 기타 정보를 저장하는 것입니다.
객체의 동작이 변경됨에 따라 변경됩니다. 다양한 잠금 상태는 다양한 기록 저장 방법에 해당합니다.
위 사진을 비교하면 총 잠금 잠금 없음 상태, 바이어스 잠금 상태, 경량 잠금 상태 및 중량 잠금 상태 의 네 가지 상태가 있으며 이는 경쟁 상황에 따라 점차 확대됩니다. 잠금 획득 및 해제의 효율성을 높이기 위해 잠금을 업그레이드할 수 있지만 다운그레이드할 수는 없습니다.
배경 소개: 대부분의 경우 잠금은 다중 스레드 경쟁이 없을 뿐만 아니라 스레드가 잠금을 획득하는 데 비용을 더 저렴하게 만들기 위해 항상 동일한 스레드에 의해 여러 번 획득됩니다. , 편향된 잠금이 도입되었습니다. 불필요한 CAS 작업을 줄입니다. 편향된 잠금은 이름에서 알 수 있듯이 스레드에 대한 첫 번째 방문으로 편향됩니다. 작업 중에 스레드에서만 동기 잠금에 액세스하면 다중 스레드 분쟁이 없으므로 스레드가 트리거될 필요가 없습니다. 동기화, 감소 감소, 감소 잠금/잠금 해제의 일부 CAS 작업(예: 대기 대기열의 일부 CAS 작업), 이 경우 바이어스 잠금이 스레드에 추가됩니다. 작업 중에 다른 스레드가 잠금을 선점하면 바이어스 잠금을 보유한 스레드가 일시 중지되고 JVM은 해당 스레드에 대한 바이어스 잠금을 제거하고 잠금을 표준 경량 잠금으로 복원합니다. 리소스에 대한 경쟁이 없을 때 동기화 기본 요소를 제거하여 프로그램의 실행 성능을 더욱 향상시킵니다.
바이어스 잠금 획득 프로세스를 이해하려면 아래 그림을 보십시오.1단계. Mark Word를 방문하여 바이어스 잠금 플래그가 1로 설정되어 있는지 확인하고 잠금 플래그가 01인지 확인합니다. 편향된 상태입니다.
2단계. 바이어스 가능 상태인 경우 스레드 ID가 현재 스레드를 가리키는지 테스트합니다. 그렇다면 5단계로 이동하고, 그렇지 않으면 3단계로 이동합니다.
Step 3. 스레드 ID가 현재 스레드를 가리키지 않는 경우 CAS 연산을 통해 잠금 경쟁을 합니다. 경쟁이 성공하면 Mark Word의 스레드 ID를 현재 스레드 ID로 설정한 다음 5를 실행하고, 경쟁이 실패하면 4를 실행합니다.
4단계. CAS가 바이어스 잠금을 획득하지 못하면 경쟁이 있음을 의미합니다. 전역 안전 지점(safepoint)에 도달하면 바이어스 잠금을 획득한 스레드가 일시 중단되고 바이어스 잠금이 경량 잠금으로 업그레이드된 후 안전 지점에서 차단된 스레드가 계속해서 동기화 코드를 실행합니다. (바이어스 잠금을 해제하면 단어가 중지됩니다.)
Step 5. 동기화 코드를 실행합니다.
바이어스 잠금 해제:
바이어스 잠금 취소는 위의 네 번째 단계에서 언급됩니다. 바이어스 잠금은 다른 스레드가 바이어스 잠금을 놓고 경쟁하려고 할 때만 바이어스 잠금을 해제합니다. 바이어스 잠금을 보유한 스레드는 바이어스 잠금을 해제하기 위해 주도권을 갖지 않습니다. 편향된 잠금을 취소하려면 전역 안전 지점(이 시점에서는 바이트코드가 실행되지 않음)을 기다려야 합니다. 먼저 편향된 잠금을 소유한 스레드를 일시 중지하고 잠금 개체가 잠긴 상태인지 확인합니다. , 그런 다음 바이어스 잠금을 취소한 후 바이어스 잠금을 이전 상태로 복원합니다. 잠금 상태(플래그 비트는 "01") 또는 경량 잠금(플래그 비트는 "00")입니다.
편향된 잠금에 적용 가능한 시나리오:
동기화 블록을 실행하는 스레드는 항상 하나만 있습니다. 실행이 완료되고 잠금이 해제되기 전에는 동기화 블록을 실행할 다른 스레드가 없을 때 사용됩니다. 경쟁이 경량 잠금으로 업그레이드되면 편향 잠금을 취소해야 합니다. 잠금, 바이어스 잠금은 많은 추가 작업을 수행합니다. 특히 바이어스 잠금을 취소하면 안전 지점에 진입하게 되어 이 경우 비활성화되어야 합니다. .
jvm 바이어스 잠금 켜기/끄기4.3, 경량 잠금 바이어스 잠금에서 업그레이드된 바이어스 잠금은 한 스레드가 동기화 블록에 들어갈 때 실행됩니다. 두 번째 스레드가 잠금 경합에 참여하면 바이어스 잠금은 경량 잠금으로 업그레이드됩니다.바이어스 잠금 켜기: -XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0 바이어스 잠금 끄기: -XX:-UseBiasedLocking
잠금 프로세스:
하지만 스레드 회전에는 CPU 소비가 필요합니다. 직설적으로 말하면 CPU가 쓸데없는 작업을 수행하기 위해 항상 CPU 회전을 점유할 수는 없으므로 최대 회전 대기 시간을 설정해야 합니다.
잠금을 보유한 스레드의 실행 시간이 최대 스핀 대기 시간을 초과하고 잠금이 해제되지 않으면 잠금을 놓고 경쟁하는 다른 스레드는 여전히 최대 대기 시간 내에 잠금을 획득할 수 없습니다. 경합하는 스레드가 자체적으로 중지됩니다. 스핀은 차단 상태로 들어갑니다.
그러나 잠금 경쟁이 치열하거나 잠금을 보유한 스레드가 동기화 블록을 실행하기 위해 오랫동안 잠금을 점유해야 하는 경우 스핀 잠금은 항상 사용되기 때문에 이때 스핀 잠금을 사용하는 것은 적합하지 않습니다. 잠금을 획득하기 전에 쓸모없는 작업을 위해 CPU를 점유하는 것은 그리 큰 함정이 아닙니다. 스레드 차단 및 일시 중지 작업의 소비보다 CPU를 확보할 수 없어 낭비가 발생합니다. CPU의.
스핀 잠금의 목적은 CPU 리소스를 해제하지 않고 점유하고 잠금이 획득될 때까지 기다려 즉시 처리하는 것입니다. 그러나 스핀 실행 시간을 선택하는 방법은 무엇입니까? 스핀 실행 시간이 너무 길면 많은 수의 스레드가 스핀 상태가 되어 CPU 리소스를 점유하게 되어 전체 시스템 성능에 영향을 미치게 됩니다. 그래서 회전수가 중요합니다. 다음 JVM의 회전수 선택, JDK1.5의 기본값은 10회, 1.6에서 적응형 스핀 잠금을 도입했는데, 적응형 스핀 잠금은 회전 시간이 고정되어 있지 않지만 이전 시간부터 이전 시간이 이전 시간에 있었다는 의미입니다. 이전 시간은 동일한 잠금의 스핀 시간과 잠금 소유자의 상태에 따라 결정됩니다. 기본적으로 스레드의 컨텍스트 전환 시간이 가장 좋은 시간으로 간주됩니다.
JDK1.6에서는 -XX:+UseSpinning이 스핀 잠금을 설정합니다. JDK1.7 이후에는 이 매개변수가 제거되고 jvm에 의해 제어됩니다.
4.3.4, 다양한 잠금 비교
추천 학습: "java 비디오 튜토리얼
"위 내용은 Java 스레드 학습을 위한 동시 프로그래밍 지식 포인트의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!