(Java) synchronized 키워드가 가지는 의미
synchronized 키워드를 사용하는 이유를 살펴보기 전에 쓰레드를 왜 여러 개 사용하는지부터 알아보자.
쓰레드를 여러 개(멀티 쓰레딩) 사용하는 이유
- CPU가 I/O(파일, 네트워크 등등) 작업에 의해 Blocking 돼서 놀고 있을 때
만약 DB를 호출하는 작업(네트워크 I/O)이 있다고 치자.
I/O가 발생하면 기본적으로 CPU는 Block된다. (idle 상태에 빠져 놀고 있다.)
이렇게 CPU가 놀고 있을 때 다른 쓰레드가 CPU를 점유한다면 CPU는 더 이상 놀지 않게 된다.
이렇게 CPU의 병목을 줄이다보면 성능을 개선할 수 있다. - 시분할 다중화를 통해 동시에 여러 작업이 처리되게 끔 보이게 할 때
문서 작성을 하면서 동시에 웹 브라우저에서 파일을 다운로드 받을 수 있는 행위는 바로 CPU에서 쓰레드를 돌아가며 작업을 처리하기 때문이다.
만약 파일 다운로드가 다 끝나야지만 문서 작성을 할 수 있다고 하면 얼마나 불편하겠는가?
물론 그렇다고 해서 진짜 동시에 여러 작업이 수행되는 게 아니라 사람이 체감 못할 정도로 시간을 쪼개서 작업을 수행하는 것이다.
멀티 쓰레드 프로그래밍을 할 때 생기는 문제
쓰레드는 프로세스 내부에 존재하기 때문에 프로세스 내부의 자원을 공유한다.
따라서 공유 자원에 대해서 동기화 이슈가 매우 중요하다.
A 쓰레드의 작업이 완전히 끝나기 전에 A’라는 자원이
다른 쓰레드에 의해 값이 바뀌게 되면 A 쓰레드는 원하는 값을 얻어낼 수 없다.
synchronized 키워드
위와 같은 문제를 해결하기 위해서는 synchronized 키워드를 사용하여 **A 쓰레드의 작업이 끝날 때까지 대기해라!**라고 명령을 내릴 수 있다.
더 나아가 **synchronized 블럭 내에 있는 공유 자원을 점유하라!**라고 이해를 하는 게 좀 더 정확하다.
하지만 여기서 멈추면 안 된다, 좀 더 자세하게 이해해야한다.
쓰레드 로컬 변수
CPU에서 명령을 수행하기 위해서는 메모리에 있는 데이터를 CPU로 가져와야한다.
하지만 메모리에 있는 데이터를 CPU로 가져오는 행위는 매우 느리므로 CPU는 캐시 메모리가 있다. (L1 캐시, L2 캐시 등등)
그리고 이 캐시 메모리를 쓰레드 로컬 변수라고 부른다.
하지만 쓰레드 로컬 변수이기 때문에 다른 쓰레드에서는 메모리에 접근을 해도 해당 쓰레드 변수의 값을 얻어올 수 없다.
따라서 공유 자원 A’에 대해 각각 쓰레드가 로컬 변수를 가질 수 있게 된다.
여기서 synchronized 키워드가 가지는 진정한 의미가 나온다.
synchronized 키워드를 사용한다는 것은 해당 블럭 내에 있는
공유 자원 A’가 쓰레드 로컬 변수에서 램으로 써지기 까지 다른 쓰레드는 대기(block)하라라는 의미를 가진다.
쓰레드 로컬 변수가 램에 써진 순간 다른 쓰레드가 램에서 해당 값을 가져와서 작업할 수 있게 된다.
즉 DB에서 커밋이 되기 전까지 해당 레코드를 조회하는 다른 커넥션은 lock에 빠지는 것과 비슷한 뉘앙스라고 받아들이면 된다.
synchronized 키워드를 남발하게 되면 쓰레드 로컬 변수가 램에 써지기 전까지 다른 쓰레드는 block이 되므로 조심해서 사용해야한다.
또한 잘못 쓴다면 무한한 block을 유발하는 dead lock 이슈도 조심해야할 것이다.