Thread를 공부하던 중에 Lock이라는 요소가 중요하다. 이 부분에 대해 더 깊게 공부하고자 정리해보고자 한다.
동시에 여러 쓰레드가 같은 자원에 접근하는 것을 제한하여 데이터의 일관성과 무결성을 유지하는데 사용되는 도구이다. java에서 java.util.concurrent.locks에서 여러 타입의 Lock을 제공한다. 동기화의 더 유연한 형태를 제공하는 장점이 있다.
lock() 메서드를 호출하여 Lock을 획득한다.unlock() 메서드를 호출하여 Lock을 해제한다. 이 부분은 반드시 finally 블록 안에 수행되어야만 함tryLock() 메서드를 제공함으로써 Lock을 얻을 수 없을 떄 다른 작업을 수행할 수 있는 유연성을 제공.필요한 경우
소스코드
public class ThreadTest {
private final Lock lock = new ReentrantLock();
private int count = 0;
public int getCount() {
return count;
}
public void increment() {
lock.lock(); // Lock을 획득
try {
count++; // critical section
} finally {
lock.unlock(); // Lock을 해제
}
}
@Test
public void test(){
ThreadTest example = new ThreadTest();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
example.increment();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
example.increment();
}
});
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Count: " + example.getCount()); // Should print 2000
}
}
출력
Count: 20000Count: 13705 // 값이 계속 바뀐다필요한 경우
소스코드
public class ThreadTest {
class ReadWriteLockExample {
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
private int data;
public void write(int newData) {
rwLock.writeLock().lock();
try {
data = newData;
} finally {
rwLock.writeLock().unlock();
}
}
public int read() {
rwLock.readLock().lock();
try {
return data;
} finally {
rwLock.readLock().unlock();
}
}
}
@Test
public void test() {
ReadWriteLockExample example = new ReadWriteLockExample();
// Write
Thread thread1 = new Thread(() -> example.write(42));
thread1.start();
try {
thread1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
// Read
Thread thread2 = new Thread(() -> {
int value = example.read();
System.out.println("Read Value: " + value);
});
thread2.start();
}
}
출력
Read Value: 42
필요한 경우
소스코드
public class ThreadTest {
class StampedLockExample {
private final StampedLock stampedLock = new StampedLock();
private int balance;
public void deposit(int amount) {
long stamp = stampedLock.writeLock();
try {
balance += amount;
} finally {
stampedLock.unlockWrite(stamp);
}
}
public int getBalance() {
long stamp = stampedLock.tryOptimisticRead();
int b = balance;
if (!stampedLock.validate(stamp)) {
stamp = stampedLock.readLock();
try {
b = balance;
} finally {
stampedLock.unlockRead(stamp);
}
}
return b;
}
}
@Test
public void test() {
StampedLockExample example = new StampedLockExample();
// Deposit
Thread thread1 = new Thread(() -> example.deposit(100));
thread1.start();
try {
thread1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
// Read Balance
Thread thread2 = new Thread(() -> {
int balance = example.getBalance();
System.out.println("Balance: " + balance);
});
thread2.start();
}
}
출력
Balance: 100
필요한 경우
소스코드
public class ThreadTest {
@Test
public void accessResource() {
int numberOfPermits = 2; // 동시에 접근할 수 있는 허용 횟수
Semaphore semaphore = new Semaphore(numberOfPermits);
Runnable task = () -> {
try {
semaphore.acquire(); // 세마포어로부터 허가를 얻음
System.out.println(Thread.currentThread().getName() + " acquired the semaphore.");
// 공유 자원에 대한 작업 수행
Thread.sleep(2000); // 잠시 대기
System.out.println(Thread.currentThread().getName() + " released the semaphore.");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release(); // 세마포어를 해제하여 다른 스레드가 접근할 수 있도록 함
}
};
// 여러 스레드 생성 및 실행
Thread thread1 = new Thread(task, "Thread 1");
Thread thread2 = new Thread(task, "Thread 2");
Thread thread3 = new Thread(task, "Thread 3");
thread1.start();
thread2.start();
thread3.start();
}
}
출력
Thread 1 acquired the semaphore.
Thread 2 acquired the semaphore.
앞서 언급한 StampedLock 언급한 Optimistic Locking에 대해서 설명하고자 한다. 그런 김에 상반된 개념인 Pessismistic Locking에 대해도 알아보고자 한다.
Optimistic Locking (낙관적 락)
Pessimistic Locking (비관적 락)
| Optimistic Locking | Pessimistic Locking | |
|---|---|---|
| Lock 획득 시기 | 데이터 변경 시 미리 Lock 을 획득 | 데이터 변경 시 미리 Lock 을 획득 |
| 동시성 | 높음(다른 사용자와 동시 접근) | 낮음(다른 사용자가 대기) |
| Lock 유지 시간 | 없음 | Lock 기간 |
| 충돌 감지 방법 | 데이터 변경 시 version 충돌 감지 | 데이터 변경 시 |
| Lock 해제 방법 | 데이터 변경 시 version 증가 | 데이터 변경 후 |
| 장점 | Lock없이 동시 접근 가능, 충돌 감지 | Lock 획득 시 데이터 안전 보장 |
| 단점 | version 충돌 발생 가능성 | 성능 저하, 대기 시간 발생 |
| 사용 분야 | 트래픽이 많고 Lock 충돌 가능성 낮은 시스템 | 금융, 예약 시스템 등 안정성 중시 |
Lock에서 빠질 수 없는 개념인 DeadLock이라는 요소를 알아보고자 한다.

DeadLock을 이해하고 해결하기 위한 예제
지금까지 Lock의 기본적인 개념부터 Java에서 사용하는 Lock관련 라이브러리들을 정리해봤다. 또 사용되는 개념인 낙관적 락 & 비관적 락, 발생할만한 문제인 DeadLock에 대해서 정리해보았다.