[CS/운영체제] 멀티스레드와 동시성 - 7부

황제연·2025년 6월 30일
0

CS학습

목록 보기
121/193
post-thumbnail

메모리 가시성(Memory Visibility)

아래와 같은 코드가 있다고 가정해보자

public class Main{

	public static void main(String[] args){
		TestTaks task = new TestTask();
		Thread thread = new Thread(task, "myWork");
		thread.start();

		sleep(2000);
		task.runFlag = false;
		System.out.println("main 종료");
	}

	static class TestTask implements Runnable{
		boolean flag = true;

		@Override
		public void run(){
			System.out.println("시작");
			while(flag){
				// 작업중...
			}
			System.out.println("task 종료");
		}
	}

}

단순하게 생각하면, 스레드가 작업하다가 2초가 지나면 runFlag가 false로 바뀌면서
task가 종료되고 main도 종료될 것으로 예상합니다

하지만 경우에 따라 task 종료가 출력되지 않고 종료될 떄가 있습니다
바로 이 문제가 메모리 가시성 문제입니다

메모리 접근 방식

일반적으로 위 로직을 main 스레드와 myWork 스레드가 각각 CPU 코어를 하나씩 점유하며,
메인메모리의 flag를 공유할 것으로 생각할 것입니다

하지만 실제 메모리의 접근 방식은 CPU의 성능을 개선하기 위해 중간에 캐시 메모리를 사용합니다
따라서 각 스레드에서는 flag의 값을 먼저 캐시메모리로 불러와서
이후 작업에 대해 캐시메모리에 있는 flag 값을 사용합니다

문제점

이어서 main 스레드에서 flag를 false로 바꾸는 작업이 이루어지고,
main스레드에 있는 캐시 메모리의 flag 값이 false로 변경됩니다

여기서 문제점은 바로 캐시 메모리의 flag값만 변경된다는 것입니다
이 값이 메인 메모리에 즉시 반영되지 않을 수 있습니다

또한 메인 메모리에 반영된다고 해도 myWork 스레드의 캐시 메모리에도 즉시 반영되지 않을 수 있습니다
이것 역시 언제 반영될지 알 수 없습니다
CPU 설계 방식과 실행환경에 따라 반영될지 여부가 결정되기 때문에, 그 여부를 정확히 알 수 없습니다

다만 주로 컨텍스트 스위칭이 될 때, 캐시 메모리도 함께 갱신되는 경우가 많스비다

메모리 가시성이란?

이렇게 멀티스레드 환경에서 한 스레드가 변경한 값이 다른 스레드에서 언제 보이는지에 대한
문제를 메모리 가시성이라고 합니다

메모리에 변경된 값이 보이는지 안 보이는지의 문제입니다

volatile

바로 이렇게 어떤 스레드에서 변경한 값이 다른 스레드에서 즉시 보이게 할 때 사용하는 것이
자바의 volatile입니다

volatile을 사용하면 값을 읽고 쓸 때, 메인 메모리에서 직접 접근하도록 합니다
대신 캐시 메모리를 사용하지 않기 때문에 약간의 성능을 포기해야합니다

public class Main{

	public static void main(String[] args){
		TestTaks task = new TestTask();
		Thread thread = new Thread(task, "myWork");
		thread.start();

		sleep(2000);
		task.runFlag = false;
		System.out.println("main 종료");
	}

	static class TestTask implements Runnable{
		volatile boolean flag = true;

		@Override
		public void run(){
			System.out.println("시작");
			while(flag){
				// 작업중...
			}
			System.out.println("task 종료");
		}
	}

}

이렇게 하면 앞선 메모리 가시성 문제를 해결할 수 있습니다
flag를 false로 바꾸는 작업에서 메인 메모리에 직접 반영되고,
메인 메모리의 데이터를 직접 조회하기 떄문에 바로 while문을 탈출 할 수 있습니다

대신 앞서 말했듯이 캐시 메모리를 사용할 때보다 성능이 느려지는 단점이 있기 때문에
필요한 곳에서만 사용하는 것이 좋습니다

자바 메모리 모델

메모리 가시성

다시 정리하면 멀티스레드 환경에서 한 스레드가 변경한 값이 다른 스레드에서
언제 보이는지에 대한 것을 메모리 가시성이라고 합니다

즉, 메모리에 변경한 값이 보이는지 안 보이는지에 대한 문제입니다

Java Memory Model (JMM)

JMM은 자바가 메모리에 어떻게 접근하고 수정하는지 규정합니다
멀티스레드 프로그래밍에서 스레드간의 상호작용도 정의합니다

다양한 내용이 있는데, 여러 스레드들의 작업 순서를 보장하는 happens-before 관계에 대한 정의가 있습니다

happen-before

happen-before 관계는 자바 메모리 모델에서 스레드간의 작업 순서를 정의하는 개념입니다
만약 A작업과 B작업이 happen-before 관계라면, A작업의 모든 메모리 변경사항은
B 작업에서 볼 수 있습니다
결론적으로 A 작업의 변경사항이 B 작업 시작전에 모두 메모리에 반영되므로
한 스레드가 수행한 작업을 다른 스레드가 참조할 때 최신상태를 보장합니다

관계가 발생하는 경우

다양한 경우가 있으며 대표적으로 프로그램 순서 규칙이나
volatile 변수 규칙도 모두 happens-before 관계를 형성합니다

참고

  • 김영한의 실전 자바 - 고급 1편
profile
Software Developer

0개의 댓글