Notice
Recent Posts
Recent Comments
Link
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

동산로의 블로그

Thread와 Happens-before relation - JAVA 본문

카테고리 없음

Thread와 Happens-before relation - JAVA

동산로 2025. 5. 13. 17:39

 

 

Happens-Before은 java의 쓰레딩(threading)에서 등장하는 개념입니다. 자바에 이런 예약어 혹은 객체가 있는 것은 아니고 일종의 개념으로만 보면 됩니다. 두 개의 연산이 happens-before 관계(relationship)에 있다는 말은, 말 그대로 연산에 순서가 있다는 말 입니다. 매우 간단한 말 이지만 우리가 모르는 컴퓨터가 알아서 하는 뒷면에는 복잡함이 숨겨져 있습니다.

 

 

 

코드의 순서와 실행 순서

 우리의 생각과 달리 코드의 순서와 실제 실행 순서는 다릅니다. 코드의 의미(semantic)을 바꾸지 않는 선에서 JVM 혹은 CPU는 최적화를 위해서 코드 진행 순서를 변경 혹은 병렬처리 할 수 있습니다. 여기서 Happens-before은 이런 일이 일어날 때에 의미를 보존하기 위해서 순차적이여만 하는 코드는 순차적으로 진행하는 것을 의미합니다. 

c = a + b;
d = e + f;
c = c + a;
d = d + e;

#위 문장의 순서가
#아래와 같이 변할 수 있습니다.
c = a + b;
c = c + a;
d = e + f;
d = d + e;

 

 하나의 쓰레드만 만드는 순차적인 프로그램에서는 매우 당연한 수순이지만 이것이 멀티 쓰레딩(Multi-threading)이 되면 조금 복잡해 집니다

 

 

 

캐쉬와 메모리의 불일치

 

 Cpu의 thread는 메모리에서 직접 정보를 가져오지 않습니다. CPU - 레지스터 - 캐시 - 메모리 순으로 정보가 오가게 됩니다. 이 때에 레지스터와 캐시는 모든 cpu가 하나의 영역 사용하는 것이 아니기 때문에 다른 쓰레드가 사용하는 레지스터, 캐시의 정보가 보이지 않습니다. 두 개의 쓰레드는 같은 정보를 가져오려고 하지만 같은 정보가 아닐 때도 있습니다. 이 문제는 volatile과 synchronized를 사용해 해결합니다.

 

 

volatile

CPU에서 하나의 변수를 참조할 때에 RAM에서 직접 가져오는 것이 아니라 캐시에 복사를 한 후에 참조를 하게 됩니다. 이 방식에 문제점은 thread별로 다른 cache를 사용하기 때문에 서로 다른 값을 주고 받을 수 있다는 점입니다. 이 문제를 해결하기 위해서 volatile이라는 키워드가 있습니다. volatile은 변수가 참조될 때 마다 메모리에서 값을 새로 받아오는 방식 입니다. 일반적으로 캐쉬에 저장하지 않고 메모리에서 값을 가져오는 방식을 사용합니다.(원천적으로 캐쉬에 저장을 금지하는 것은 아닙니다.)

 

또한 자바 런타임에서 volatile 변수를 사용할 때에는 happens-before visibility guarantee of volatile를 만족하도록 합니다. 즉 최적화를 하는데 있어서 제약을 거는 셈 입니다. volatile로 선언된 변수가 사용 될 때에는 그 전과 그 후를 구분하여, 확실하게 실행 순서가 전 후로 나뉘도록 보장합니다.

 

 

다만 이 경우도 문제가 있습니다. 서로 다른 쓰레드가 '동시'에 참조를 하면 서로 다른 값을 가질 수 있습니다. 예를 들어 a++ 라는 연산이 두 쓰레드에서 동시에 작동하는 경우 리턴값이 2가 아닌 1이 될 수 있습니다. 

 

이 경우 변수에 대해서 쓰레드 간 가시성(visibility)는 있지만 원자성(atomicity) 없다고 합니다.

 

 

synchronized

그렇다면 가시성과 원자성 모두 챙긴 경우도 있을 터 입니다. 그것이 바로 synchronized 키워드 입니다. synchronized 키워드를 사용하면 객체 혹은 메소드에 대해서 상호배제 락을 할 수 있습니다.(mutual exclusion lock)

크게 두가지 활용 방법이 있습니다. 선언 시에 synchronized를 사용해서 클래스 혹은 메소드를 lock하는 방법과, 객체 하나를 지정 한 후 코드 블럭을 만들어서 락 하는 방법이 있습니다. 

 

synchronized 키워드를 사용해서 클래스/메소드 선언하기

 

synchronized 키워드를 사용해서 객체를 lock 하고 블럭 구문 실행하기.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

https://jenkov.com/tutorials/java-concurrency/java-happens-before-guarantee.html

https://www.geeksforgeeks.org/happens-before-relationship-in-java/

https://en.wikipedia.org/wiki/Happened-before

https://www.geeksforgeeks.org/volatile-keyword-in-java/