동시성 문제를 제어하는 방법
📌 정의
멀티 스레드 환경에서 여러 스레드가 동시에 하나의 자원을 공유할 때 동시성 문제, 데드락 등 여러 문제가 발생한다. 해당 문제 중 동시성 문제를 제어하는 방법을 알아보자.
1️⃣ 암시적 잠금
하나의 스레드가 해당 메소드를 실행하고 있을 때 다른 스레드가 해당 메소드를 실행하지 못하고 대기하게 하는 방법. 잠금은 메소드, 변수에 각 각 걸 수 있다.
😎 예제
1
2
3
4
5
6
7
class Count {
private int count;
public synchronized int view() {
return count++;
}
}
위의 예제는 메소드 잠금이다.
1
2
3
4
5
6
7
8
class Count {
private Integer count;
public int view() {
synchronized (this.count) {
return count++;
}
}
}
2️⃣ 명시적 잠금
synchronized
키워드 없이 명시적으로 ReentrantLock
을 사용하는 잠금.
운영체제에서 임계영역과 비슷한 느낌이다.
😎 예시
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class CountingTest {
public static void main(string[] args) {
Count count = new Count();
for (int i = 0; i < 100; i++) {
new Thread() {
public void run() {
for(int j = 0; j < 1000; j++) {
count.getLock().lock();
System.out.println(count.view());
count.getLock().unlock();
}
}
}.start();
}
}
}
class Count {
private int count = 0;
private Lock lock = new ReentrantLock();
public int view() {
return count++;
}
public Lock getLock() {
return lock;
}
}
3️⃣ volatile
스레드들이 자원에 read, write를 진행할 때 항상 메모리에 접근하지 않는다. 성능의 향상을 위해 CPU 캐스에 값을 저장하기 때문에 해당 값이 메모리에 저장된 실제 값고 항상 일치하는지 보장할 수 없다.
메인 메모리에 저장된 실제 자원의 값을 볼 수 있는 개념을 자원의 가시성이라고 부른다.
volatile
은 위와 같은 CPU 캐시 사용을 막는다. 변수에 volatile 키워드를 붙여주면 해당 변수는 캐시에 저장되는 대상에서 제외된다. 캐시 사용으로 인한 테이터 불일치를 막는다.
4️⃣ Concurrent
Atomic~~와 같은 클래스는 단순한 증감연산을 메소드로 제공해준다. 해당 클래스의 메소드는 내부적으로 Thread-safe하게 구조화되어 있다.
Concurrent~~와 같은 컬랙션들은 락을 사용할 때 발생하는 성능 저하를 최소한으로 만들어두었다.