[Java 동시성 싸움]------Java 메모리 모델이 발생하기 전에
지난 블로그([Deadly Java Concurrency] - 휘발성 구현 원리 심층 분석)에서 LZ는 스레드 로컬 메모리와 메인 메모리의 존재로 인해 재정렬과 결합되어 다중 스레드 환경 가시성 문제가 있습니다. 따라서 동기화와 잠금을 올바르게 사용한다면 스레드 A는 언제 스레드 B에 표시되는 변수 a를 수정합니까?
모든 시나리오에서 스레드에 의해 수정된 변수가 다른 스레드에 표시되는 시기를 지정할 수는 없지만, 이 규칙은 JDK 5부터 시작됩니다. 여러 스레드 간의 메모리 가시성을 설명하기 위해 이전 발생 개념을 사용합니다.
JMM에서 한 작업의 결과를 다른 작업에서 볼 수 있어야 하는 경우 두 작업 사이에 사전 발생 관계가 있어야 합니다.
데이터에 경쟁이 있는지, 스레드가 안전한지 여부를 판단하는 기본 원칙은 매우 중요합니다. 동시 환경에서 두 작업 간의 충돌이 발생합니다. 모든 질문입니다. 이전 발생에 대해 조금 알아보기 위해 간단한 예를 들어보겠습니다.
i = 1; //线程A执行 j = i ; //线程B执行
j는 1인가요? 스레드 A(i = 1)의 작업이 스레드 B(j = i)의 작업 이전에 발생한다고 가정하면 스레드 B의 실행 후에 j = 1이 참이어야 한다고 판단할 수 있습니다. 이러한 작업이 존재하지 않는 경우 사전 발생 원칙이 적용되면 j = 1이 되지 않습니다. 이 원칙이 확립되어야 합니다. 이것이 사전 발생 원칙의 힘입니다.
선행 원칙은 다음과 같이 정의됩니다.
1. 작업이 다른 작업 전에 발생하면 첫 번째 작업의 실행 결과가 두 번째 작업에 표시됩니다. 그리고 첫 번째 작업의 실행 순서는 두 번째 작업 이전입니다.
2. 두 작업 사이에는 사전 발생 관계가 있습니다. 이는 사전 발생 원칙에 따라 설정된 순서대로 실행되어야 한다는 의미는 아닙니다. 재정렬 후의 실행 결과가 이전 발생 관계에 따른 실행 결과와 일치하면 이러한 재정렬은 불법이 아닙니다.
다음은 사전 발생 원칙 규칙입니다.
프로그램 순서 규칙: 스레드 내에서는 코드 순서에 따라 작업이 작성됩니다. 쓰기 작업에서 앞부분이 먼저 발생합니다.
잠금 규칙: 나중에 동일한 잠금 작업 전에 잠금 해제 작업이 발생합니다.
휘발성 변수 규칙: 변수에 대한 쓰기 작업이 먼저 발생하고 이후 변수에 대한 읽기 작업이 발생합니다.
전송 규칙: 작업 A가 먼저 발생하면 작업 B가 먼저 발생하고 작업 B가 먼저 발생합니다. 작업 C의 경우 작업 A가 작업 C보다 먼저 발생한다고 결론을 내릴 수 있습니다.
스레드 시작 규칙: 이 스레드의 모든 작업에 대해 Thread 개체의 start() 메서드가 먼저 발생합니다. ;
스레드 중단 규칙: 중단된 스레드의 코드가 인터럽트 이벤트 발생을 감지하면 스레드 Interrupt() 메서드 호출이 먼저 발생합니다. 🎜>
스레드 종료 규칙: 스레드의 모든 작업은 스레드가 종료될 때 먼저 발생합니다. Thread.join() 메서드의 끝과 Thread.isAlive의 반환 값을 통해 스레드가 실행을 종료했음을 감지할 수 있습니다. ();- 객체 종료 규칙: 객체 초기화는 finalize() 메서드 시작 부분에서 먼저 발생합니다. 위의 각 규칙에 대한 자세한 내용("Java Virtual Machine에 대한 심층적인 이해, 12장"에서 발췌):
프로그램 순서 규칙
: 코드 조각의 결과 단일 스레드에서 실행되는 순서가 지정됩니다. 가상머신과 프로세서가 명령어를 재정렬하기 때문에 이는 실행 결과라는 점에 유의하세요(재정렬에 대해서는 나중에 자세히 설명합니다). 순서가 바뀌더라도 프로그램의 실행 결과에는 영향을 미치지 않으므로 프로그램의 최종 실행 결과는 순차적 실행 결과와 일치합니다. 따라서 이 규칙은 단일 스레드에만 유효하며 다중 스레드 환경에서는 정확성을 보장할 수 없습니다.
잠금 규칙
: 이 규칙은 단일 스레드 환경이든 다중 스레드 환경이든 관계없이 잠금이 잠겨 있으면 먼저 잠금 해제 작업을 수행해야 합니다. 잠금 작업을 수행할 수 있습니다.휘발성 변수 규칙
: 이는 휘발성이 스레드 가시성을 보장한다는 것을 나타내는 중요한 규칙입니다. 일반인의 관점에서 보면 스레드가 먼저 휘발성 변수를 쓴 다음 스레드가 변수를 읽는 경우 쓰기 작업은 읽기 작업 전에 발생해야 합니다.전이적 규칙
: 전이 원칙이 전이적임을 보여줍니다. 즉, A는 B 전에 발생하고 B는 C 전에 발생하고 A는 C 전에 발생합니다스레드 시작 규칙
: 스레드 A가 실행 중에 ThreadB.start()를 실행하여 스레드 B를 시작한다고 가정합니다. 그러면 스레드 A가 공유 변수를 수정하면 스레드 B가 스레드 B 다음에 수정됩니다. 실행이 시작됩니다.스레드 종료 규칙
: 스레드 A가 실행 중에 ThreadB.join()을 공식화하여 스레드 B가 종료될 때까지 기다린 다음 종료 전에 스레드 B가 공유 변수를 수정하는 것은 다음과 같다고 가정합니다. 스레드 A는 반환 후 표시될 때까지 기다립니다.위의 8가지 규칙은 기본 Java가 사전 발생 관계를 충족하는 규칙이지만, 이들로부터 사전 발생 관계를 충족하는 다른 규칙을 추론할 수 있습니다.
将一个元素放入一个线程安全的队列的操作Happens-Before从队列中取出这个元素的操作
将一个元素放入一个线程安全容器的操作Happens-Before从容器中取出这个元素的操作
在CountDownLatch上的倒数操作Happens-Before CountDownLatch#await()操作
释放Semaphore许可的操作Happens-Before获得许可操作
Future表示的任务的所有操作Happens-Before Future#get()操作
向Executor提交一个Runnable或Callable的操作Happens-Before任务开始执行操作
这里再说一遍happens-before的概念:如果两个操作不存在上述(前面8条 + 后面6条)任一一个happens-before规则,那么这两个操作就没有顺序的保障,JVM可以对这两个操作进行重排序。如果操作A happens-before操作B,那么操作A在内存上所做的操作对操作B都是可见的。
下面就用一个简单的例子来描述下happens-before原则:
private int i = 0;public void write(int j ){ i = j; }public int read(){ return i; }
我们约定线程A执行write(),线程B执行read(),且线程A优先于线程B执行,那么线程B获得结果是什么?;我们就这段简单的代码一次分析happens-before的规则(规则5、6、7、8 + 推导的6条可以忽略,因为他们和这段代码毫无关系):
由于两个方法是由不同的线程调用,所以肯定不满足程序次序规则;
两个方法都没有使用锁,所以不满足锁定规则;
变量i不是用volatile修饰的,所以volatile变量规则不满足;
传递规则肯定不满足;
所以我们无法通过happens-before原则推导出线程A happens-before线程B,虽然可以确认在时间上线程A优先于线程B指定,但是就是无法确认线程B获得的结果是什么,所以这段代码不是线程安全的。那么怎么修复这段代码呢?满足规则2、3任一即可。
happen-before原则是JMM中非常重要的原则,它是判断数据是否存在竞争、线程是否安全的主要依据,保证了多线程环境下的可见性。
下图是happens-before与JMM的关系图(摘自《Java并发编程的艺术》)
参考资料
周志明:《深入理解Java虚拟机》
方腾飞:《Java并发编程的艺术》
在上篇博客(【死磕Java并发】—–深入分析volatile的实现原理)LZ提到过由于存在线程本地内存和主内存的原因,再加上重排序,会导致多线程环境下存在可见性的问题。那么我们正确使用同步、锁的情况下,线程A修改了变量a何时对线程B可见?
我们无法就所有场景来规定某个线程修改的变量何时对其他线程可见,但是我们可以指定某些规则,这规则就是happens-before,从JDK 5 开始,JMM就使用happens-before的概念来阐述多线程之间的内存可见性。
在JMM中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须存在happens-before关系。
happens-before原则非常重要,它是判断数据是否存在竞争、线程是否安全的主要依据,依靠这个原则,我们解决在并发环境下两操作之间是否可能存在冲突的所有问题。下面我们就一个简单的例子稍微了解下happens-before ;
i = 1; //线程A执行 j = i ; //线程B执行
j 是否等于1呢?假定线程A的操作(i = 1)happens-before线程B的操作(j = i),那么可以确定线程B执行后j = 1 一定成立,如果他们不存在happens-before原则,那么j = 1 不一定成立。这就是happens-before原则的威力。
happens-before原则定义如下:
1. 如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。
2. 两个操作之间存在happens-before关系,并不意味着一定要按照happens-before原则制定的顺序来执行。如果重排序之后的执行结果与按照happens-before关系来执行的结果一致,那么这种重排序并不非法。
下面是happens-before原则规则:
程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作;
锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作;
volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作;
传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C;
线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作;
线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生;
线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行;
对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始;
我们来详细看看上面每条规则(摘自《深入理解Java虚拟机第12章》):
程序次序规则:一段代码在单线程中执行的结果是有序的。注意是执行结果,因为虚拟机、处理器会对指令进行重排序(重排序后面会详细介绍)。虽然重排序了,但是并不会影响程序的执行结果,所以程序最终执行的结果与顺序执行的结果是一致的。故而这个规则只对单线程有效,在多线程环境下无法保证正确性。
锁定规则:这个规则比较好理解,无论是在单线程环境还是多线程环境,一个锁处于被锁定状态,那么必须先执行unlock操作后面才能进行lock操作。
volatile变量规则:这是一条比较重要的规则,它标志着volatile保证了线程可见性。通俗点讲就是如果一个线程先去写一个volatile变量,然后一个线程去读这个变量,那么这个写操作一定是happens-before读操作的。
传递规则:提现了happens-before原则具有传递性,即A happens-before B , B happens-before C,那么A happens-before C
线程启动规则:假定线程A在执行过程中,通过执行ThreadB.start()来启动线程B,那么线程A对共享变量的修改在接下来线程B开始执行后确保对线程B可见。
线程终结规则:假定线程A在执行的过程中,通过制定ThreadB.join()等待线程B终止,那么线程B在终止之前对共享变量的修改在线程A等待返回后可见。
上面八条是原生Java满足Happens-before关系的规则,但是我们可以对他们进行推导出其他满足happens-before的规则:
将一个元素放入一个线程安全的队列的操作Happens-Before从队列中取出这个元素的操作
将一个元素放入一个线程安全容器的操作Happens-Before从容器中取出这个元素的操作
在CountDownLatch上的倒数操作Happens-Before CountDownLatch#await()操作
释放Semaphore许可的操作Happens-Before获得许可操作
Future表示的任务的所有操作Happens-Before Future#get()操作
向Executor提交一个Runnable或Callable的操作Happens-Before任务开始执行操作
这里再说一遍happens-before的概念:如果两个操作不存在上述(前面8条 + 后面6条)任一一个happens-before规则,那么这两个操作就没有顺序的保障,JVM可以对这两个操作进行重排序。如果操作A happens-before操作B,那么操作A在内存上所做的操作对操作B都是可见的。
下面就用一个简单的例子来描述下happens-before原则:
private int i = 0;public void write(int j ){ i = j; }public int read(){ return i; }
我们约定线程A执行write(),线程B执行read(),且线程A优先于线程B执行,那么线程B获得结果是什么?;我们就这段简单的代码一次分析happens-before的规则(规则5、6、7、8 + 推导的6条可以忽略,因为他们和这段代码毫无关系):
由于两个方法是由不同的线程调用,所以肯定不满足程序次序规则;
两个方法都没有使用锁,所以不满足锁定规则;
变量i不是用volatile修饰的,所以volatile变量规则不满足;
传递规则肯定不满足;
所以我们无法通过happens-before原则推导出线程A happens-before线程B,虽然可以确认在时间上线程A优先于线程B指定,但是就是无法确认线程B获得的结果是什么,所以这段代码不是线程安全的。那么怎么修复这段代码呢?满足规则2、3任一即可。
happen-before原则是JMM中非常重要的原则,它是判断数据是否存在竞争、线程是否安全的主要依据,保证了多线程环境下的可见性。
다음 그림은 이전과 JMM의 관계입니다("The Art of Java Concurrent 프로그래밍"에서 발췌)
위는 [Dead Java Concurrency]입니다. --- --Java 메모리 모델의 내용은 이전에 발생합니다. 더 많은 관련 내용은 PHP 중국어 웹사이트(www.php.cn)를 참고하세요!

핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

Video Face Swap
완전히 무료인 AI 얼굴 교환 도구를 사용하여 모든 비디오의 얼굴을 쉽게 바꾸세요!

인기 기사

뜨거운 도구

메모장++7.3.1
사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전
중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기
강력한 PHP 통합 개발 환경

드림위버 CS6
시각적 웹 개발 도구

SublimeText3 Mac 버전
신 수준의 코드 편집 소프트웨어(SublimeText3)

뜨거운 주제











Java의 Smith Number 가이드. 여기서는 정의, Java에서 스미스 번호를 확인하는 방법에 대해 논의합니다. 코드 구현의 예.

이 기사에서는 가장 많이 묻는 Java Spring 면접 질문과 자세한 답변을 보관했습니다. 그래야 면접에 합격할 수 있습니다.

Java 8은 스트림 API를 소개하여 데이터 컬렉션을 처리하는 강력하고 표현적인 방법을 제공합니다. 그러나 스트림을 사용할 때 일반적인 질문은 다음과 같은 것입니다. 기존 루프는 조기 중단 또는 반환을 허용하지만 스트림의 Foreach 메소드는이 방법을 직접 지원하지 않습니다. 이 기사는 이유를 설명하고 스트림 처리 시스템에서 조기 종료를 구현하기위한 대체 방법을 탐색합니다. 추가 읽기 : Java Stream API 개선 스트림 foreach를 이해하십시오 Foreach 메소드는 스트림의 각 요소에서 하나의 작업을 수행하는 터미널 작동입니다. 디자인 의도입니다

Java의 TimeStamp to Date 안내. 여기서는 소개와 예제와 함께 Java에서 타임스탬프를 날짜로 변환하는 방법에 대해서도 설명합니다.

캡슐은 3 차원 기하학적 그림이며, 양쪽 끝에 실린더와 반구로 구성됩니다. 캡슐의 부피는 실린더의 부피와 양쪽 끝에 반구의 부피를 첨가하여 계산할 수 있습니다. 이 튜토리얼은 다른 방법을 사용하여 Java에서 주어진 캡슐의 부피를 계산하는 방법에 대해 논의합니다. 캡슐 볼륨 공식 캡슐 볼륨에 대한 공식은 다음과 같습니다. 캡슐 부피 = 원통형 볼륨 2 반구 볼륨 안에, R : 반구의 반경. H : 실린더의 높이 (반구 제외). 예 1 입력하다 반경 = 5 단위 높이 = 10 단위 산출 볼륨 = 1570.8 입방 단위 설명하다 공식을 사용하여 볼륨 계산 : 부피 = π × r2 × h (4

PHP와 Python은 각각 고유 한 장점이 있으며 선택은 프로젝트 요구 사항을 기반으로해야합니다. 1.PHP는 간단한 구문과 높은 실행 효율로 웹 개발에 적합합니다. 2. Python은 간결한 구문 및 풍부한 라이브러리를 갖춘 데이터 과학 및 기계 학습에 적합합니다.

PHP는 서버 측에서 널리 사용되는 스크립팅 언어이며 특히 웹 개발에 적합합니다. 1.PHP는 HTML을 포함하고 HTTP 요청 및 응답을 처리 할 수 있으며 다양한 데이터베이스를 지원할 수 있습니다. 2.PHP는 강력한 커뮤니티 지원 및 오픈 소스 리소스를 통해 동적 웹 컨텐츠, 프로세스 양식 데이터, 액세스 데이터베이스 등을 생성하는 데 사용됩니다. 3. PHP는 해석 된 언어이며, 실행 프로세스에는 어휘 분석, 문법 분석, 편집 및 실행이 포함됩니다. 4. PHP는 사용자 등록 시스템과 같은 고급 응용 프로그램을 위해 MySQL과 결합 할 수 있습니다. 5. PHP를 디버깅 할 때 error_reporting () 및 var_dump ()와 같은 함수를 사용할 수 있습니다. 6. 캐싱 메커니즘을 사용하여 PHP 코드를 최적화하고 데이터베이스 쿼리를 최적화하며 내장 기능을 사용하십시오. 7

Java는 초보자와 숙련된 개발자 모두가 배울 수 있는 인기 있는 프로그래밍 언어입니다. 이 튜토리얼은 기본 개념부터 시작하여 고급 주제를 통해 진행됩니다. Java Development Kit를 설치한 후 간단한 "Hello, World!" 프로그램을 작성하여 프로그래밍을 연습할 수 있습니다. 코드를 이해한 후 명령 프롬프트를 사용하여 프로그램을 컴파일하고 실행하면 "Hello, World!"가 콘솔에 출력됩니다. Java를 배우면 프로그래밍 여정이 시작되고, 숙달이 깊어짐에 따라 더 복잡한 애플리케이션을 만들 수 있습니다.
