78. 공유 중인 가변 데이터는 동기화해 사용하라
📌 1. 발표 전 알아야 할 개념
1) 스레드(Thread)
프로세스
: 실행중인 프로그램. 자바 JVM은 하나의 프로그램으로 실행되며, 동시에 여러 작업을 수행하기 위해 멀티 스레드 지원스레드
: 프로세스 안에서 동시에 여러 작업을 처리할 수 있게 해주는 실행 단위.출처 : https://www.codelatte.io/courses/java_programming_basic/3YOZQA8ZUYBPYNHY
스레드를 만들자
public static void main(String[] args) {
Runnable runn = new MyThread();
// [생성] 스레드 생성
Thread th1 = new Thread(runn);
// 스레드 실행 (run() 메서드가 비동기적 실행)
th1.start();
}
// [세팅] Runnable 인터페이스를 통해서 구현
class Test implements Runnable {
public void run() {
// 작업
}
}
2) 동기 vs 비동기
동기(Synchronous)
: 작업이 끝날 때까지 기다렸다가 다음 작업을 실행함비동기(Asynchronous)
: 작업이 끝나는 것을 기다리지 않고 동시에 다른 작업도 수행함
3) 공유자원(Synchronization)과 임계 영역(Critical Section)
여러 스레드가 함께 접근하는 데이터나 자원
임계 영역
: 공유 자원에 접근하는 코드 영역
4) 상호배제(Mutual Exclution)과 가시성(Visibility), 원자성(Atomicity), 배타적 수행(Exclusive Execution)
상호배제
: 한 순간에 오직 하나의 스레드만 공유 자원에 접근할 수 있게 하는 제약상호 배제를 하지 않으면? 여러 스레드가 동시에 데이터를 읽고 쓰면서 무결성이 깨짐
가시성
: 한 스레드가 변경한 내용을 다른 스레드에게 보이게 함자바에서는 각 스레드가 CPU 캐시나 레지스터에 값을 들고 있을 수 있음
변경 즉시 메인 메모리에 반영되지 않으면? 다른 스레드는 예전 값을 계속 보게됨
원자성
: 하나의 작업이 중간에 끼어들 수 없이 한 번에 수행되는 성질배타적 수행
: 한 번에 하나의 스레드만 특정 코드 블록을 실행할 수 있도록 보장하는 것
📕 2. 동기화(Synchronization)
동기화
: 여러 스레드가 공유 자원에 안전하게 접근할 수 있도록 순서를 조정하는 매커니즘 -> (쉽게 말하면..) 여러 스레드가 같은 데이터를 동시에 건드리지 못하도록 순서를 조율한다락(Lock)
: 여러 스레드가 동시에 공유 자원에 접근하지 못하게 막고, 하나의 스레드만 접근할 수 있도록 잠그는 장치락 동기화는 내부적으로
락
을 통해 상호 배제를 구현한다**동기화는 배타적 실행 뿐만 아니라, 스레드의 안정적인 통신에 꼭 필요하다**
만약 동기화가 안된다면...?
public class BrokenCounter {
private int count = 0;
public void increment() {
count++; // count 값 읽고 - 1 더하고 - 결과 다시 저장
}
public int getCount() {
return count;
}
}
두 스레드가 동시에 실행된다면, 1, 2와 같이 순차적으로 실행되는 것이 아니라, 둘 다 같은 값을 읽고, 똑같이 1을 더하고, 같은 값을 다시 저장할 수 있다. -> 증가가 실행이 안됨!
🔥 3. 동기화를 해보자 - 동기화 방법
3-1. Synchronized
Synchronized
Java에서 가장 기본적인 동기화 매커니즘
한 번에 하나의 스레드만 해당 블록에 접근하게 만든다 (배타적 수행 보장)
public class BrokenCounter {
private int count = 0;
public synchronized void increment() {
count++; // 동기화
}
public synchronized int getCount() {
return count;
}
}
synchronized
를 사용하면 JVM은 메모리 동기화와 락(lock)을 자동으로 처리해줌동기화된 메서드는 하나의 스레드만 접근할 수 있음
3-2. volatile
volatile
volatile
키워드를 이용하여 변수의 가시성 문제를 해결할 수 있음.변경된 값을 메인 메모리에 즉시 반영, 다른 스레드가 최신 값을 볼 수 있도록 보장
public class VolatileFlag {
private volatile boolean stop = false;
public void stopThread() {
stop = true;
}
public void run() {
while (!stop) {
// 실행 내용
}
}
}
단, 복합 연산(++, += 등)에는 원자성 보장을 하지 않는다. -> syncronized 사용
// 잘못된 사용 예 - count++는 원자적이지 않음
private volatile int count = 0;
public void increment() {
count++; // 원자성 보장 안 됨
}
🔧 4. 동기화와 관련된 주의사항
4-1. Thread.stop()은 절대 사용하지 마라
Thread.stop()
은 스레드를 강제 종료 시킴. 이 과정에서 락을 해제하지 않고 종료되어 다른 스레드가 락이 걸린 상태에서 영원히 대기할 수 있음데이터 무결성이 깨지고, 프로그램이 비정상 종료될 수 있음
락을 잡은 스레드가 락을 반환하지 못한 채로 종료 ->
데드락
발생volatial
플래그나interrupt
()`를 활용해 안전하게 종료 조건을 제어해야 한다.
4-2. 읽기와 쓰기 모두가 동기화 되지 않으면 아무 의미가 없다
어떤 스레드가
synchronized
로 값을 썼는데, 다른 스레드가 동기화되지 않은 상태로 그 값을 읽는다면 최신 값이 보장되지 않는다.
4-3. long과 double은 원자적이지 않을 수 있다
long과 double은 64비트 크기 단위
JVM의 기본 연산 단위는 32비트이기 때문에 두 번 나누어 읽고 쓸 수 있다
멀티 스레드 환경에서는 한 스레드가 long, double 값을 읽는 중 다른 스레드가 쓰게 되면 이상한 값을 읽을 수 있다..!
volatile
을 이용해 64비트 전체를 한 번에 읽고 쓰도록 강제하자
=> 결론 : 공유 중인 가변 데이터는 항상 동기화하되, 가능하면 공유하지 말자
🤖 최종 결론
공유 중인 가변 데이터는 항상 동기화하라
단일 변수의 가시성만 필요할 땐 volatile, 복잡한 동기화와 관련되어 있을 경우 : synchronized
최선의 방법은 가변 데이터 공유를 피하는 것!
😶🌫️ 느낀점
스프링에서 동기화 관련하여 상태 관리나 동시성에 대해 알아보고 싶다
Last updated