여러 스레드를 사용하는 상황에서 프로그래밍을 할 때, 주의해야할 상황이 하나 있다.
바로 여러 스레드가 변수를 공유해서 사용하는 상황이다.
무엇을 조심해야할까?
첫번째는, 한 스레드가 접근할 때 다른 스레드는 접근하지 못하게 하는 것이다. 이를 배타적 수행이라고 한다.
두번째는, 한 스레드가 변경한 값을 다른 스레드도 볼 수 있게 하는 것이다. 이를 스레드간 통신이라고 한다.
배타적 수행과 스레드간 통신이 꼭 지켜져야하는 것은 아니다.
하지만 상황을 잘 이해하고 필요하다면 꼭 적용해야 내가 원하는 결과가 나올 수 있다.
차례대로 살펴보자.
배타적 수행
언제 한 스레드가 공유 데이터에 접근할 때 배타적으로 수행해야할까?
스레드 1이 Number객체를 변경 중이라서 상태가 완전하지 않은 순간의 객체를,
스레드 2가 접근하여 보거나, 변경하지 못하게 해야할 때, 배타적으로 수행되어야한다.
이는 Number객체에 접근하는 스레드 1의 메서드가 Number객체에 락을 걸고, 락을 건 메서드는 객체의 상태를 확인하고 필요하면 수정한다. 그리고 락을 풀어주는 형식으로 진행된다.
그러면 어떤 스레드의 메서드들도 이 객체가 완전하지 않은 순간을 볼 수 없다.
스레드간 통신
다시 한 번 살펴보자. "한 스레드가 변경한 값을 다른 스레드도 볼 수 있게 한다"고 했다. 이게 도대체 무슨 말일까 하는 생각이 들 것이다.
찬찬히 살펴보자. 이 부분은 Java가 데이터를 저장하는 방식과 Main memory와 관련이 있다.
Java가 변수에 값을 저장하면 어떤 일이 일어날까?
값을 저장했기 때문에 당연히 그 값은 하드웨어 어느 부분에 저장이 되어야 하고, 그 위치는 Main memory가 될 것이다. 그리고 해당 값을 읽을 때도 당연히 저장한 Main memory에서 읽어올 것이다.
문제는, Java가 그 행위를 바로바로 하지 않는다는 점이다.
Java는 데이터를 저장할 때, cache를 두어 cache에 읽고 쓰기를 수행한다. 그러다가 어느 순간에 Main memory에 cache에 저장되어 있는 값들을 일치시켜 준다.
하지만 Main memory에 일치시켜주는 순간을 우리는 알 수가 없다.
문제는 값을 읽어올 때도 마찬가지라는 것이다.
따라서 스레드 1에서 아무리 Number객체의 내용을 변경하여도,
우선적으로 cache1에만 반영되어 있을 뿐 Main memory에 반영이 되었는지는 알 수 없을 뿐만 아니라,
스레드 2가 Number객체의 값을 읽어들일 때도, cache2에 있는 값을 우선적으로 읽을 뿐,
스레드 1이 변경한 값을 읽지는 못한다.
그럼 어떻게 해야할까?
volatile 키워드를 사용하면 된다.
class Number {
public static volatile int number = 0;
public static volatile boolean chk = false;
}
volatile이 붙어있는 데이터는 Java에서 Main memory에 직접 쓰고 읽기는 수행한다.
따라서 스레드 1이 Number객체의 내용을 변경하면 그 내용이 바로 Main memory에 쓰여지고,
스레드 2가 Number객체의 내용을 읽을 때도 Main memory에서 직접 읽게 된다.
ref.
https://www.youtube.com/watch?v=nhYIEqt-jvY
이펙티브 자바 3/E. item78
'TIL' 카테고리의 다른 글
[Git] git remote, 원격 저장소 확인, 추가, 수정, 삭제 (0) | 2021.10.20 |
---|---|
SQL 쿼리 실행 원리 (0) | 2021.09.05 |
[Git] Fork, Pull Request, Clone, Push (0) | 2021.08.01 |
[Spring] Filter란, (0) | 2021.07.30 |
[Git] fork, pull request, rebase (0) | 2021.07.26 |