본문 바로가기

개발/Java & Spring

[번역정리] Java Concurrency:Java Memory Model(write by Dmytro Timchenko)

Future 인터페이스 공부하다보니 Concurrency 부분이 또 헷갈리기도해서 기선님이 추천한 포스팅을 가볍게 읽어보려고 했는데, 이펙티브 자바 동시성 파트와 연결되는 부분이 많아서 열심히 읽다보니 번역까지 하고 있었다 ㅎㅅㅎ ..

혼자보기는 조금 아까워서 추천할 겸 블로그 포스팅 !  시리즈 물(?)이어서 더 재밌는거같다. 찬찬히 계속 읽어봐야징 

medium.com/javarevisited/java-concurrency-java-memory-model-96e3ac36ec6b

 

Java Concurrency: Java Memory Model

In this article, we’ll cover the Java Memory Model and how it works with computer hardware.

medium.com

 

 

 

 

 

Java Memeory Model 은 여러 스레드들이 어떻게 메모리를 공유하고 Communication 하는지를 보여준다.

1995년 도입된 기존 자바 메모리모델은 이미 좋지 않다고 널리 인식되고 있는데, 런타임 최적화를 방해하고 코드의 안정성을 보장하지 않기 때문이다. 그러다 2004년 Java 5가 나오면서 Java Community Process를 통해 JSR-133으로 업데이트 되었고, 이때의 Java Memory Model이 오늘날 자바의 메모리 모델이다.

동시성 프로그램을 올바르게 디자인하고 싶다면 Java Memory Model을 이해하는 것은 매우 중요하다. Jave Memory Model을 통해 어떻게, 그리고 언제 다양한 스레드들이 다른 스레드들에 의해 공유되는 변수를 바라보는지, 그리고 필요하다면 어떻게 그들을 동기적으로 접근할 지를 알 수 있다.

 

 

The Internal Java Memory Model

JVM 의 Java Memory Model 은 메모리를 스레드 스택과 힙으로 나눈다.

 

JVM에서 동작하는 모든 스레드는 자기 자신의 스레드 스택을 가지며. 스레드 스택은 현재 실행 지점에 도달하기 위해 호출한 메서드에 대한 정보를 포함한다.

 

스레드 스택은 해당 스레드에서 불린 모든 메서드의 지역변수도 포함한다. 스레드는 오직 자기 자신의 스레드에만 접근할 수 있고 각 스레드에 의해 생성된 지역변수는 다른 스레드에는 보이지 않는다.

 

모든 기본형(boolean, byte, short, char, int, long, float, double) 지역변수는 스레드 스택에만 저장되고 다른 스레드에는 보이지 않는다.

 

힙은 자바 애플리케이션에서 생성된 모든 객체들을 포함한다. 물론, Byte, Integer, Long 과 같은 기본형의 Wrapper Type 도 마찬가지이다. 객체가 생성된 뒤 지역변수에 할당되거나 다른 객체의 멤버변수로서 생성되는 것도 힙에 할당되지만 이것은 문제가 되지 않는다. (힙은 모든 스레드가 공유함에도)

 

 

 

기본 타입의 지역변수는 온전히 스레드 스택에 저장된다.

지역변수는 객체의 레퍼런스를 가질 수도 있는데, 이런 경우에 그 레퍼런스(지역변수)는 스레드 스택에 저장되고 실제 객체는 힙에 저장된다.

객체는 메서드를 포함할 수 있고 이 메서드는 지역 변수를 가질 수 있는데, 이 지역변수들 역시 쓰레드 스택에 저장된다.(힙에 저장되는 것이 아니다.)

객체의 멤버변수는 기본타입이든 레퍼런스타입이든 상관 없이 객체와 함께 힙에 저장된다.

정적 클래스 변수는 클래스 정보와 함꼐 힙에 저장된다.

힙에 저장된 객체는 그 객체의 레퍼런스를 가진모든 스레드에 의해 접근할 수 있다. 스레드가 객체에 접근하면, 그 객체의 멤버변수에도 접근할 수 있다. 만약 두개의 스레드가 같은 객체의 메서드를 동시에 호출하면, 두 스레드 다 객체의 멤버 변수에 접근하게 된다.

 

 

 

Hardware Memory Architecture

현대 하드웨어 메모리 구조는 자바의 메모리 구조와 어느정도 다른 부분이 있다. Java Memory Model이 어떻게 동작하는지를 이해하기 위해 하드웨어 메모리 구조를 이해하는 것도 중요하다.

 

 

현대의 컴퓨터는 두개 이상의 CPU를 가지고 또 각각의 CPU는 multi-core 를 가지곤 한다. 이게 핵심인데, 두개 이상의 CPU를 가진 현대의 컴퓨터에서 동시에 하나의 스레드 이상이 실행될 수 있다는 것이다. 하드웨어적으로 각 CPU는 주어진 시간에 하나의 스레드를 동작시킬 수 있다. 즉, Java Application이 "멀티 스레드" 환경 이라면, 각 CPU의 하나의 스레드가 동시에 당신의 애플리케이션을 실행한다는 것이다. [-> CPU 한개당 스레드 한개인데 멀티스레드! ]

 

각 CPU는 레지스터 셋을 포함하고, 이 메인 메모리에서 동작하는 것보다 레지스터에서 연산을 수행하는 것이 더 빠르다. CPU가 레지스터에 접근하는 시간이 메인메모리에 접근하는 시간보다 더 빠르기 떄문이다.

 

각 CPU는 CPU cache memory 계층을 가지는데, RAM보다 더 빠르게 cache memory 에 접근할 수 있디.(상단 그림) 그러나 레지스터보다 빠르진 않다. 그래서 CPU cache memory 는 내부 레지스터와 메인 메모리 사이의 속도를 가진다고 말한다.

 

컴퓨터는 RAM이라고 불리는 메인 메모리 공간을 가지는데, 모든 CPU 가 이 메인 메모리에 접근한다. 일반적으로 RAM은 cache memory 보다 크다.

 

일반적으로 CPU가 RAM에 접근할 필요가 있을때, RAM에서 읽는 부분을 CPU cache로 넣을 것이다. 그리고 그 캐시의 읽는 부분을 다시 내부 레지스터로 넣고난 뒤 연산을 수행한 것이다. CPU가 결과를 다시 RAM에 쓸 필요가 있을 때, 내부 레지스터에서 캐시 메모리로 값을 flush 하고, 어느 시점에 다시 그 값을 RAM으로 flush 한다.

 

캐시 메모리에 저장된 값들은 일반적으로 CPU가 캐시메모리에 다른 어떤 값을 저장할 필요가 있을 때 RAM으로 flush 된다. CPU 캐시는 한번에 메모리의 일부를 읽어들일 수 있고, 한번에 flush 할 수 있다. 업데이트 될 때마다 전체 캐시를 읽거나 쓰는 것이 아니다. 일반적으로 캐시는 cache lines 이라고 불리는 더 작은 메모리 블록을 업데이트한다. 한개 이상의 캐시라인은 캐시 메모리로 읽히고 RAM으로 다시 flush 된다.

 

Java Memory Model and Hardware

Java Memory Model과 하드웨어 메모리 구조는 다르다. 하드웨어 메모리 구조는 스레드스택, 힙으로 구분되지 않는다. 하드웨어 측면에서, 스레드 스택과 힙은 둘다 메인 메모리에 위치되어있다. 스레드 스택과 힙의 일부분은 가끔 CPU 캐시와 내부 CPU 레지스터에 나타나기도 한다.

객체와 변수가 컴퓨터의 다양한 공간에 저장되어있을 수 있을 때, 문제가 발생할 수 있다.

  • Visibility of thread updates to shared variables
  • Race Condition when reading, checking, and writing shared variables

 

Visiblity Of Shared Objects

만일 두개 이상의 스레드가 적절한 volatile 이나 동기화를 사용하지 않고객체를 공유한다면, 한 스레드에 의한 공유 객체의 업데이트는 다른 스레드에 보이지 않을 수도 있다.

 

공유 변수가 처음에는 메인 메모리에 저장되어있다고 가정하자. 하나의 스레드는 공유 객체를 CPU 캬시로 읽어들인다. 그때 공유 객체에 변경이 일어난다. CPU 캐시가 메인 메모리로 다시 flush 되지 않는한, 공유 객체의 변경된 버전은 다른 CPU에서 동작하고 있는 스레드에 보이지 않는다. 각 스레드는 공유 객체에 대한 자신만에 복사본을 가지게되고, 그 복사본은 다른 CPU 캐시에 위치하게 된다.

 

 

문제를 해결하기 위해 Java의 synchronized block 을 사용할 수 있다. 동기화 불록은 하나의 스레드만 critical section에 접근 할 수 있도록 보장한다. 또, 동기화 블록은 동기화 블록 내의 모든 변수가 값을 메인 메모리로부터 읽도록 보장한다. 그리고 스레드가 동기화 블록 밖으로 나가면, volatile선언과 관계 없이 모든 업데이트된 변수는 메인 메모리로 다시 flush 된다.

 

그리고 이 포스팅의 원인이 된 ,, 잇-슈 

https://github.com/Java-Bom/ReadingRecord/issues/142

 

[아이템 78] 동기화에서 통신과 자바메모리 모델의 관계 · Issue #142 · Java-Bom/ReadingRecord

p.415 이는 한 스레드가 만든 변화가 다른 스레드에게 언제 어떻게 보이는지를 규정한 자바의메모리 모델 때문이다 질문 : 자바의 메모리 모델과 상관관계가 궁금해. 여기에 링크 연결되어있는데

github.com