🎁 미리 알면 좋은 지식
- 자바 동기화의 가시성(visibility)문제
가시성 문제는 스레드가 변경한 값이 메인 메모리에 저장되지 않아, 다른 스레드가 이 값을 볼 수 없는 상황을 말한다. 여러 개의 스레드가 동시에 같은 작업을 수행하지 않는다고 해도cnt++
와 같은 연산을 수행할 때 문제가 발생할 수도 있다. 왜냐하면 메모리에서 read-modify-write과정이 있기 때문이다. 이 과정동안 한 스레드가 쓴 값을 다른 스레드 무조건 볼 수 있다고 보장할 수 없다.
java변수를 메인 메모리에서 바로 읽어(write) 조작하고, 메인 메모리에 반영(write)하겠다고 명시하는 키워드. 이 키워드가 없다면, 변수 값을 변경해도 언제 다시 메인 메모리에 반영되지 보장할 수가 없다.
public volatile int cnt = 0;
volatile은 하나의 스레드만 write를 하고, 다른 스레드는 read하는 상황에서 적합하다.
자바의 모든 객체는 '고유의 락'을 가진다. 이를 모니터, 모니터 락, 뮤텍스라고도 한다. 고유락을 이용한 synchronized블록은 동시성 문제를 해결하는 가장 간편한 방법이다.
1. synchronized를 사용하지 않은 경우
public class Counter {
private int count;
public int increase() {
return ++count; // 스레드 안전하지 않은 연산.
}
}
2.synchronized를 사용한 경우 -> 변수로 접근하는 스레드를 제어할 수 있다.
방법1)
public class Counter {
private int count;
public int increase() {
synchronized(this) {
return ++count;
}
}
}
방법2)
public class Counter {
private int count;
public synchronized int increase() {
return ++count;
}
}
자바의 락 획득은 스레드 단위로 일어나기 때문에, 이미 락을 획득한 스레드는 같은 락을 얻기 위해 대기할 필요가 없다.
synchronized
를 이용한 동기화를 구조적인 락이라고 한다. synchronized
블록 단위로 락의 획득과 해제가 일어나므로 구조적이라고 표현한다. synchronized
블록을 진입할 때 락의 획득이 일어나고, 블록을 벗어날 때 락의 해제가 일어난다. 따라서 구조적인 락 A와 B가 있을 때 A 획득 -> B 획득 -> B 해제 -> A 해제는 가능하지만 A 획득 -> B 획득 -> A 해제 -> B 해제는 불가능 하다.
synchronized와 동일하게 가시성과 상호 배제 기능을 제공한다. 명시적인 락은 synchronized만으로 해결할 수 없는 복잡한 상황에서 사용하기 위한 방법이다. (*구조적인 락은 해결하지 못하는 A 획득 -> B 획득 -> A 해제 -> B 해제 구조도 가능하게 해준다!)
import java.util.concurrent.locks.ReentrantLock;
Lock lock = new ReentrantLock();
lock.lock();
try { // 임계영역
.
.
.
} finally {
lock.unlock();
}
✔ 복잡한 구조도 해결하는 명시적인 락도 단점이 있다!
- 명시적인 락을 사용할 경우 try문에서 예외가 발생하면 락이 해제되지 않는 경우가 발생한다. 따라서 락이 해제될 수 있도록 catch,finally 블록에서 락을 해제하는 것이 중요하다.
- 동시성 접근을 제한하지 않아도 되는 경우에도 제한하는 경우가 있다. 예를 들어, read연산이다. read연산은 락의 획득과는 무관하게 여러 스레드가 동시에 접근해도 데이터의 일관성을 해치지 않는다. 하지만 명시적인 락의 경우, read연산을 수행하기 위해 다수의 스레드가 대기해야 하는 문제가 발생한다. 이를 위해 Read/Write Lock으로 해결할 수 있다.
+)Read/Write Lock
동작방식
A 스레드가 read 연산을 수행하면 read-lock을 획득한다.
객체가 read-lock 상태일 때 B 스레드가 read 연산을 수행하고 read-lock을 획득한다.
C 스레드가 write 연산을 수행하면 write-lock을 획득하고 모든 연산이 완료되어 write-lock이 해제될 때 까지 다른 스레드는 read/write lock을 획득할 수 없다.
https://github.com/Songwonseok/CS-Study/blob/main/Language/Java/Intrinsic%20Lock.md