Java에서 사용되는 메모리 펜스 란 무엇입니까?


18

방법을 이해하려고 노력하는 동안 SubmissionPublisher( 자바 SE (10), 오픈 JDK 소스 코드 | 문서 ), 버전 9에서 자바 SE에 추가 된 새로운 클래스가 구현 된, 나는에 몇 가지 API 호출을 우연히 발견 VarHandle나는 이전에 인식되지 않았습니다 :

fullFence, acquireFence, releaseFence, loadLoadFencestoreStoreFence.

특히 기억 장벽 / 울타리의 개념에 관한 연구를 한 후에 (나는 이전에 들어 보았지만 결코 사용하지 않았으므로 의미에 익숙하지 않았습니다), 나는 그들이 무엇을위한 것인지에 대한 기본적인 이해가 있다고 생각합니다. . 그럼에도 불구하고 내 질문이 오해에서 생길 수 있으므로 처음부터 올바르게 이해하고 싶습니다.

  1. 메모리 장벽은 읽기 및 쓰기 작업과 관련하여 재정렬 제약 조건입니다.

  2. 메모리 배리어는 읽기 또는 쓰기 중 하나 또는 둘 다에 대한 제약 조건을 설정했는지에 따라 단방향 및 양방향 메모리 배리어의 두 가지 주요 범주로 분류 할 수 있습니다.

  3. C ++는 다양한 메모리 장벽을 지원 하지만 이것들이 제공하는 것과는 일치하지 않습니다 VarHandle. 그러나 사용 가능한 일부 메모리 장벽 은 해당 C ++ 메모리 장벽 과 호환되는 순서 효과VarHandle제공 합니다.

    • #fullFence 호환 atomic_thread_fence(memory_order_seq_cst)
    • #acquireFence 호환 atomic_thread_fence(memory_order_acquire)
    • #releaseFence 호환 atomic_thread_fence(memory_order_release)
    • #loadLoadFence#storeStoreFence호환되는 C ++ 카운터 부분이 없다

호환 이라는 단어 는 여기서 의미론이 세부 사항과 관련하여 분명히 다르기 때문에 여기서 중요한 것으로 보입니다. 예를 들어, 모든 C ++ 장벽은 양방향이지만 Java의 장벽은 (필요하지는 않습니다).

  1. 대부분의 메모리 장벽에는 동기화 효과가 있습니다. 이는 특히 사용 된 배리어 유형과 다른 스레드에서 이전에 실행 된 배리어 명령에 따라 다릅니다. 장벽 명령어가 하드웨어에 특정한 의미를 갖기 때문에 더 높은 수준 (C ++) 장벽을 고수하겠습니다. 예를 들어 C ++에서 릴리스 장벽 명령어 이전에 작성된 변경 사항은 획득을 실행하는 스레드에서 볼 수 있습니다. 명령을 .

내 가정이 맞습니까? 그렇다면 결과 질문은 다음과 같습니다.

  1. 사용 가능한 메모리 장벽을 VarHandle 이 어떤 종류의 메모리 동기화 유발합니까?

  2. 메모리 동기화를 유발하는지 여부에 관계없이 Java에서 재정렬 제약 조건이 유용한 것은 무엇입니까? Java 메모리 모델은 휘발성 필드, 잠금 또는 다음 VarHandle과 같은 작업의 순서와 관련하여 이미 매우 강력한 보증을 제공합니다.#compareAndSet 이 관련된 .

예를 찾고있는 경우 : 위에서 언급 한 BufferedSubscription내부 클래스 SubmissionPublisher(위의 링크 된 소스)는 1079 줄에 완전한 울타리를 설정했습니다 (함수 growAndAdd; 링크 된 웹 사이트는 조각 식별자를 지원하지 않으므로 CTRL + F 만 사용하십시오) ). 그러나 그것이 무엇인지 불분명합니다.


1
나는 대답을 시도했지만 매우 간단하게 말하면 사람들은 Java보다 약한 모드를 원하기 때문에 존재합니다 . 오름차순으로 다음과 같습니다 plain -> opaque -> release/acquire -> volatile (sequential consistency)..
유진

답변:


11

이것은 주로 대답이 아니며 실제로는 처음에 주석으로 만들고 싶었지만 볼 수 있듯이 너무 깁니다. 그것은 내가 이것에 대해 많이 의문을 품고, 많은 독서와 연구를했으며,이 시점에서 나는 안전하게 말할 수 있습니다 : 이것은 복잡합니다. 나는 jcstress 로 여러 테스트를 작성 하여 실제로 어떻게 작동하는지 (어셈블리 생성 된 코드를 보면서) 이해 했으며 일부는 어떻게 든 의미가 있지만 일반적으로 주제는 결코 쉽지 않습니다.

가장 먼저 이해해야 할 것 :

JLS (Java Language Specification)는 어디에나 장벽을 언급하지 않습니다 . 이것은 자바의 경우 구현 세부 사항이 될 것 입니다. 시맨틱 전에 발생하는 측면에서 실제로 작동 합니다. JMM (Java Memory Model)에 따라이를 올바르게 지정 하려면 JMM이 상당히 많이 변경되어야합니다 .

현재 진행중인 작업입니다.

둘째, 여기 표면을 긁고 싶다면 이것이 가장 먼저 볼 것 입니다. 대화는 믿어지지 않습니다. 내가 가장 좋아하는 부분은 Herb Sutter가 5 개의 손가락을 들고 "이것이 얼마나 많은 사람들이 이걸로 정확하게 작업 할 수 있는지"입니다. 복잡성에 대한 힌트를 얻을 수 있습니다. 그럼에도 불구하고 이해하기 쉬운 몇 가지 사소한 예제가 있습니다 ( 다른 메모리 보장에 신경 쓰지 않고 올바르게 증가하는 것만 고려 하는 여러 스레드에 의해 업데이트 된 카운터 ).

또 다른 예는 (Java에서) volatile플래그가 스레드를 중지 / 시작하도록 제어 하려는 경우 입니다. 알다시피, 고전 :

volatile boolean stop = false; // on thread writes, one thread reads this    

java로 작업하는 경우이 코드가 없으면 volatile 깨짐을 알 수 있습니다 (예를 들어 이중 검사 잠금이없는 이유를 읽을 수 있음). 그러나 고성능 코드를 작성하는 일부 사람들에게는 이것이 너무 많다는 것을 알고 있습니까? volatile읽기 / 쓰기는 순차적 일관성을 보장합니다. 강력한 보장이 있으며 일부 사람들은 더 약한 버전을 원합니다.

스레드 안전 플래그이지만 휘발성이 아닌가? 예, 정확히 : VarHandle::set/getOpaque.

그리고 당신은 누군가가 그것을 필요로 하는지 의문을 가질 것 입니까? 모든 사람이에 의해 피기 백되는 모든 변경 사항에 관심이있는 것은 아닙니다 volatile.

우리가 어떻게 자바에서 이것을 달성 할 수 있는지 봅시다. 우선, 이러한 이국적인 것들이 API에 이미 존재했습니다 AtomicInteger::lazySet. 이것은 Java 메모리 모델에서 지정되지 않았으며 명확한 정의가 없습니다 . 아직도 사람들은 그것을 사용했습니다 ( 더 많은 독서를 위해 LMAX, afaik 또는 이것 ). IMHO는 AtomicInteger::lazySet입니다 VarHandle::releaseFence(또는 VarHandle::storeStoreFence).


왜 누군가가 이것들을 필요로하는지 대답 해 보자 .

JMM에는 기본적으로 필드에 액세스하는 두 가지 방법이 있습니다 ( 일반휘발성 ( 순차적 일관성 보장 )). 언급 한 모든 방법은이 두 릴리스 / 획득 시맨틱 사이에 무언가를 가져옵니다 . 사람들이 실제로 이것을 필요로 하는 경우가 있다고 생각 합니다.

릴리스 / 획득 에서 훨씬 이완은 불투명 것 입니다. 나는 여전히 완전히 이해하려고 노력하고 있습니다.


따라서 결론 (당신의 이해는 상당히 정확합니다, btw) : 당신이 자바에서 이것을 사용할 계획이라면-현재 사양이 없다면, 당신 자신의 위험에 처하십시오. 그것들을 이해하고 싶다면 C ++의 동등한 모드가 시작됩니다.


1
lazySet고대의 답변에 연결 하여 의미를 파악하려고 시도하지 마십시오 . 현재 문서 는 요즘의 의미를 정확하게 말합니다. 또한 JMM에 두 가지 액세스 모드 만 있다고 말하는 것은 오해의 소지가 있습니다. 우리는 volatile readvolatile write 를 가지고 있으며, 이는 함께 발생 하는 관계를 설정할 수 있습니다 .
Holger

1
나는 그것에 대해 더 많은 것을 쓰는 중이었습니다. 그 고려 CAS는 전체 장벽처럼 행동, 읽기와 쓰기 모두이며,이 요구되는 편안한 이유를 이해 할 수있다. 예를 들어 잠금을 구현할 때 첫 번째 조치는 잠금 수에서 cas (0, 1)이지만 시맨틱 (휘발성 읽기와 같은) 만 획득하면되지만 잠금을 해제하기위한 최종 쓰기 0은 릴리스 시맨틱 (휘발성 쓰기와 같은)을 가져야합니다. ), 잠금 해제와 후속 잠금 사이에 발생합니다 . 획득 / 해제는 다른 잠금을 사용하는 스레드와 관련하여 휘발성 읽기 / 쓰기보다 훨씬 약합니다.
Holger

1
@Peter Cordes : volatile키워드가 있는 첫 번째 C 버전 은 Java 5 년 C99 이지만 C ++ 03에도 메모리 모델이없는 유용한 의미론이 여전히 부족합니다. C ++에서 "원자"라고 부르는 것은 Java보다 훨씬 더 젊습니다. 그리고 volatile키워드는 원자 업데이트를 의미하지도 않습니다. 그래서 왜 그런 이름을 지어야합니까?
Holger

1
@PeterCordes, 아마도 그것을 혼동하고 restrict있지만 __volatile키워드가 아닌 컴파일러 확장을 사용 하기 위해 작성 해야 했던 때를 기억 합니다. 아마도 C89를 완전히 구현하지 않았습니까? 말하지 마 난 옛날. Java 5 이전 volatile에는 C에 훨씬 더 가까웠지만 Java에는 MMIO가 없었기 때문에 그 목적은 항상 멀티 스레딩 이었지만 Java 5 이전 의미는 그다지 유용하지 않았습니다. 따라서 의미론과 같은 릴리스 / 취득이 추가되었지만 여전히 원자 적이지는 않습니다 (원자 업데이트는 그 위에 빌드 된 추가 기능입니다).
Holger

2
@Eugene 대한 , 내 예는 획득 될 잠금을 위해 CA를 사용하여 특정했다. 카운트 다운 래치는 해제 의미론으로 원자 감소를 가져오고 스레드가 획득 펜스를 삽입하고 최종 조치를 실행하는 0에 도달합니다. 물론 전체 펜스가 필요한 원자 업데이트의 경우도 있습니다.
Holger
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.