F-LAB JAVA · 4주차 · Phase 5 · 정교한 락: LockSupport와 ReentrantLock
★ 마스터 Unit (실무 직결) + 🏆 Phase 5 완주
이 Unit을 끝내면 다음을 답할 수 있어야 한다.
tryLock()은 락 획득을 시도하되 실패하면 즉시 (또는 타임아웃 후) 포기하는 메서드로, 무한 대기 대신 포기·재시도를 가능하게 하여 데드락을 회피한다.
boolean tryLock()은 즉시 시도하여 실패 시 false 를 반환하고,boolean tryLock(time, unit)은 지정 시간만큼 시도한 후 실패하면 false 를 반환한다.
데드락은 두 스레드가 서로 다른 순서로 두 락을 잡으려 할 때 (A 가 락1 보유·락2 대기, B 가 락2 보유·락1 대기) 발생하며,lock()을 쓰면 둘 다 영원히 대기 하지만tryLock()을 쓰면 한 쪽이 포기하고 보유 락을 풀어 데드락을 회피 한다.
lock()만 쓰는 시스템에서 데드락이 발생하면 회복 방법이 없어 재시작뿐 이므로, tryLock 으로 타임아웃·재시도를 두거나 모든 스레드가 락을 항상 같은 순서로 획득 하도록 (락 순서 일관성) 설계해야 한다.
tryLock(timeout) 실패 후에는 백오프 (backoff) 후 재시도, 대체 경로, 또는 작업 포기 등의 전략을 사용한다.
데드락 = 좁은 외나무다리:
상황:
- A 가 왼쪽에서, B 가 오른쪽에서
- 다리 중간에서 마주침
- 서로 비켜주길 기다림
- 영원히 멈춤 (데드락)
lock() = 고집:
- "당신이 비켜요"
- 둘 다 안 비킴
- 영원히 (회복 불가)
tryLock() = 양보:
- "5초 기다려봤는데 안 되네"
- 한 명이 뒤로 물러남 (포기)
- 다시 시도 (재시도)
- 데드락 회피
락 순서 일관성 = 규칙:
- "항상 키 작은 사람이 먼저"
- 순서 정하면 마주침 없음
- 데드락 원천 차단
→ tryLock = 포기/재시도로 데드락 회피, 락 순서 일관 = 원천 방지.
1. tryLock()의 두 형태
2. 데드락의 4가지 조건
3. 데드락 시나리오
4. lock()이면 영원히 멈춤
5. tryLock()으로 회피
6. lock()만 쓰는 시스템의 회복
7. tryLock 실패 후 전략
8. 락 순서 일관성
9. 면접 + 자기 점검 + 마스터 50문항 + Phase 5 완주
// 1. 즉시 시도
boolean tryLock();
// 2. 타임아웃 시도
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 즉시 시도 (실패 시 false)
if (lock.tryLock()) {
try {
// 락 획득 성공
doWork();
} finally {
lock.unlock();
}
} else {
// 락 획득 실패 (즉시)
log.info("이미 처리중인 작업이 있습니다.");
// 대기 안 함
}
// 타임아웃 시도
try {
if (lock.tryLock(5, TimeUnit.SECONDS)) {
try {
// 5초 내 획득 성공
doWork();
} finally {
lock.unlock();
}
} else {
// 5초 내 실패
log.warn("락 획득 타임아웃");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
lock vs tryLock:
lock():
- 무한 대기
- 반드시 획득 (또는 영원히)
tryLock():
- 즉시 시도
- 실패 시 false (대기 X)
tryLock(time):
- 시간 내 시도
- 실패 시 false
// tryLock 반환값 반드시 확인
boolean acquired = lock.tryLock();
if (acquired) {
try {
// 임계 영역
} finally {
lock.unlock(); // 획득했을 때만
}
}
// 획득 못 했으면 unlock 호출 X (예외)
@Service
public class TryLockBasics {
private final ReentrantLock lock = new ReentrantLock();
// 즉시 시도 — 중복 처리 방지
public boolean processIfAvailable(Shipment shipment) {
if (lock.tryLock()) { // 즉시
try {
doProcess(shipment);
return true;
} finally {
lock.unlock();
}
}
log.info("이미 처리중: {}", shipment.getId());
return false; // 다른 스레드가 처리 중
}
// 타임아웃 시도
public boolean processWithTimeout(Shipment shipment) throws InterruptedException {
if (lock.tryLock(5, TimeUnit.SECONDS)) { // 5초
try {
doProcess(shipment);
return true;
} finally {
lock.unlock();
}
}
log.warn("처리 타임아웃: {}", shipment.getId());
return false;
}
private void doProcess(Shipment s) { }
}
tryLock()의 두 형태는?
답:
1. 즉시 tryLock():
타임아웃 tryLock(time):
vs lock():
반환값:
데드락 4가지 조건 (모두 충족 시):
1. 상호 배제 (Mutual Exclusion)
- 자원을 한 번에 하나만
2. 점유 대기 (Hold and Wait)
- 자원 보유한 채 다른 자원 대기
3. 비선점 (No Preemption)
- 자원 강제 회수 불가
4. 순환 대기 (Circular Wait)
- 서로 순환적으로 대기
상호 배제:
- synchronized, lock
- 한 스레드만 임계 영역
점유 대기:
- 락A 보유 + 락B 대기
비선점:
- 락 강제로 뺏기 불가
- 스스로 풀어야
순환 대기:
- A→B→A 순환
- 서로 기다림
데드락 방지 — 조건 하나 깨기:
1. 상호 배제 깨기:
- 어려움 (락의 본질)
2. 점유 대기 깨기:
- 모든 락 한 번에 (전부 또는 포기)
3. 비선점 깨기:
- tryLock (타임아웃 → 포기)
4. 순환 대기 깨기:
- 락 순서 일관 (가장 실용적)
데드락 4조건:
스레드 A: 락1 보유 ──점유대기──→ 락2 대기
↑
순환대기
↓
스레드 B: 락2 보유 ──점유대기──→ 락1 대기
상호 배제 + 점유 대기 + 비선점 + 순환 대기
→ 데드락
// 데드락 4조건이 충족되는 예
public class DeadlockConditions {
private final ReentrantLock lockA = new ReentrantLock();
private final ReentrantLock lockB = new ReentrantLock();
// 스레드 1
public void method1() {
lockA.lock(); // 1. 상호 배제 (A)
try {
// 2. 점유 대기 (A 보유한 채)
lockB.lock(); // B 대기
try {
// ...
} finally {
lockB.unlock();
}
} finally {
lockA.unlock(); // 3. 비선점 (스스로만)
}
}
// 스레드 2 (반대 순서)
public void method2() {
lockB.lock(); // B 먼저
try {
lockA.lock(); // A 대기 (4. 순환 대기)
try {
// ...
} finally {
lockA.unlock();
}
} finally {
lockB.unlock();
}
}
// method1 + method2 동시 → 데드락
}
데드락의 4가지 조건은?
답:
1. 4가지:
모두 충족:
방지:
tryLock:
계좌 이체 데드락:
스레드 1: transfer(accA, accB)
- accA 락 획득
- accB 락 대기
스레드 2: transfer(accB, accA)
- accB 락 획득
- accA 락 대기
→ 서로 대기 → 데드락
public class TransferDeadlock {
static class Account {
final ReentrantLock lock = new ReentrantLock();
int balance;
}
// ❌ 데드락 위험
public void transfer(Account from, Account to, int amount) {
from.lock.lock(); // from 먼저
try {
to.lock.lock(); // to 대기
try {
from.balance -= amount;
to.balance += amount;
} finally {
to.lock.unlock();
}
} finally {
from.lock.unlock();
}
}
// 데드락 발생:
// 스레드 1: transfer(accA, accB) → accA → accB
// 스레드 2: transfer(accB, accA) → accB → accA
// → 순환 대기
}
이체 데드락:
스레드 1: transfer(A, B)
A.lock() 성공
B.lock() 대기 ───┐
│ (B 는 스레드 2 보유)
스레드 2: transfer(B, A)
B.lock() 성공
A.lock() 대기 ───┘
(A 는 스레드 1 보유)
서로 대기 → 영원히
데드락 발생 타이밍:
타이밍 의존 (비결정적):
- 동시에 두 transfer
- 각자 첫 락 획득
- 두 번째 락 대기
가끔 발생:
- 운 나쁘면 데드락
- 재현 어려움
- 운영 중 갑자기
@Service
public class ShipmentTransferDeadlock {
// ❌ 데드락 위험 — 계정 간 잔액 이체
public void transferBalance(ShipmentAccount from, ShipmentAccount to,
BigDecimal amount) {
from.lock.lock();
try {
to.lock.lock(); // 순서 불일치 시 데드락
try {
from.balance = from.balance.subtract(amount);
to.balance = to.balance.add(amount);
} finally {
to.lock.unlock();
}
} finally {
from.lock.unlock();
}
}
// transferBalance(accA, accB) 와
// transferBalance(accB, accA) 동시 → 데드락
static class ShipmentAccount {
final ReentrantLock lock = new ReentrantLock();
BigDecimal balance = BigDecimal.ZERO;
}
}
데드락 시나리오는?
답:
1. 시나리오:
이체 예:
타이밍:
결과:
lock() 데드락:
두 스레드 모두 lock() 사용:
- 첫 락 획득
- 두 번째 락 무한 대기
→ 서로 대기
→ 영원히 멈춤
→ 회복 불가
lock() 데드락 회복 불가:
- 타임아웃 X (무한 대기)
- 인터럽트 X (lock() 은)
- 자원 강제 회수 X (비선점)
→ 회복 방법 없음
→ 재시작뿐
lock() 데드락:
스레드 1: A.lock() 성공
B.lock() [무한 대기──────────]
↑ 영원히
스레드 2: B.lock() 성공
A.lock() [무한 대기──────────]
↑ 영원히
→ 둘 다 영원히
→ 회복 불가
운영 영향:
- 두 스레드 영원히 점유
- 관련 자원 락
- 스레드 풀 고갈 가능
- 서비스 장애
진단:
- jstack → "deadlock" 감지
- 하지만 회복 불가 (재시작)
$ jstack <pid>
# 데드락 감지:
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor ... (Account B)
which is held by "Thread-2"
"Thread-2":
waiting to lock monitor ... (Account A)
which is held by "Thread-1"
# jstack 이 데드락 알려줌
# 하지만 lock() 은 회복 불가
@Service
public class LockDeadlockProblem {
private final ReentrantLock lockA = new ReentrantLock();
private final ReentrantLock lockB = new ReentrantLock();
// ❌ lock() — 데드락 시 회복 불가
public void process1() {
lockA.lock();
try {
sleep(100);
lockB.lock(); // 무한 대기 (데드락 시)
try {
doWork();
} finally {
lockB.unlock();
}
} finally {
lockA.unlock();
}
}
public void process2() {
lockB.lock(); // 반대 순서
try {
sleep(100);
lockA.lock(); // 무한 대기
try {
doWork();
} finally {
lockA.unlock();
}
} finally {
lockB.unlock();
}
}
// 동시 호출 시 데드락 → 재시작뿐
private void doWork() { }
private void sleep(long ms) { try { Thread.sleep(ms); } catch (Exception e) {} }
}
lock()이면 영원히 멈추는 이유는?
답:
1. 무한 대기:
회복 불가:
운영 영향:
진단:
tryLock 데드락 회피:
두 번째 락을 tryLock 으로:
- 실패 시 첫 락도 풀기
- 재시도
→ 한 쪽이 양보
→ 순환 대기 깨짐
→ 데드락 회피
// ✓ tryLock 으로 데드락 회피
public boolean transfer(Account from, Account to, int amount)
throws InterruptedException {
while (true) {
if (from.lock.tryLock()) { // 첫 락 시도
try {
if (to.lock.tryLock()) { // 두 번째 시도
try {
from.balance -= amount;
to.balance += amount;
return true; // 성공
} finally {
to.lock.unlock();
}
}
// to 실패 → from 도 풀고 재시도
} finally {
from.lock.unlock();
}
}
// 둘 다 못 얻음 → 잠시 후 재시도
Thread.sleep(10); // 백오프
}
}
회피 메커니즘:
스레드 1: from.tryLock() 성공
to.tryLock() 실패
→ from 풀기 (양보)
→ 재시도
스레드 2: 그 사이 to, from 획득
→ 처리 완료
한 쪽 양보 → 데드락 깨짐
tryLock 회피:
스레드 1: A.tryLock() 성공
B.tryLock() 실패 → A 풀기 (양보)
[잠시 대기]
재시도...
스레드 2: B 보유
A.tryLock() 성공 (1 이 풀었으니)
처리 완료
B, A 풀기
스레드 1: 재시도 → A, B 획득 → 완료
→ 데드락 회피 (양보 + 재시도)
// 타임아웃 tryLock 회피
public boolean transferWithTimeout(Account from, Account to, int amount)
throws InterruptedException {
long timeout = 1000;
if (from.lock.tryLock(timeout, TimeUnit.MILLISECONDS)) {
try {
if (to.lock.tryLock(timeout, TimeUnit.MILLISECONDS)) {
try {
from.balance -= amount;
to.balance += amount;
return true;
} finally {
to.lock.unlock();
}
}
} finally {
from.lock.unlock();
}
}
return false; // 타임아웃 → 포기 (데드락 회피)
}
@Service
public class TryLockDeadlockAvoidance {
// ✓ tryLock — 데드락 회피
public boolean transferBalance(ShipmentAccount from, ShipmentAccount to,
BigDecimal amount) throws InterruptedException {
int retries = 0;
while (retries < 10) {
if (from.lock.tryLock(100, TimeUnit.MILLISECONDS)) {
try {
if (to.lock.tryLock(100, TimeUnit.MILLISECONDS)) {
try {
from.balance = from.balance.subtract(amount);
to.balance = to.balance.add(amount);
return true;
} finally {
to.lock.unlock();
}
}
// to 실패 → from 풀고 재시도
} finally {
from.lock.unlock();
}
}
retries++;
Thread.sleep(ThreadLocalRandom.current().nextInt(50)); // 랜덤 백오프
}
log.warn("이체 실패 (재시도 초과)");
return false;
}
static class ShipmentAccount {
final ReentrantLock lock = new ReentrantLock();
BigDecimal balance = BigDecimal.ZERO;
}
}
tryLock()으로 회피하는 원리는?
답:
1. 원리:
메커니즘:
타임아웃:
재시도:
lock() 만 쓰는 시스템 데드락 회복:
근본적으로 회복 불가:
- 타임아웃 X
- 인터럽트 X (lock())
유일한 방법:
- 프로세스 재시작
회복 = 재시작:
데드락 발생:
- 두 스레드 영원히
- 자원 점유
회복:
- 서버 재시작
- 데드락 스레드 강제 종료
- (다른 방법 없음)
비용:
- 다운타임
- 진행 중 작업 손실
사후 회복 불가 → 사전 방지:
lock() 데드락은 회복 불가하므로
사전 방지가 유일한 해결.
방지:
1. 락 순서 일관 (섹션 8)
2. tryLock 사용 (타임아웃)
3. 락 최소화
4. 단일 락
// 데드락 감지 (ThreadMXBean)
public class DeadlockDetector {
public void detectDeadlock() {
ThreadMXBean bean = ManagementFactory.getThreadMXBean();
long[] deadlocked = bean.findDeadlockedThreads();
if (deadlocked != null) {
log.error("데드락 감지! {} 스레드", deadlocked.length);
ThreadInfo[] infos = bean.getThreadInfo(deadlocked);
for (ThreadInfo info : infos) {
log.error("Deadlocked: {}", info.getThreadName());
}
// 알림 발송 → 운영자가 재시작 판단
// (자동 회복 불가)
}
}
}
// ❌ lock() (회복 불가)
public void process() {
lockA.lock();
try {
lockB.lock(); // 데드락 시 무한
try { } finally { lockB.unlock(); }
} finally { lockA.unlock(); }
}
// ✓ tryLock (회피 가능)
public boolean processSafe() throws InterruptedException {
if (lockA.tryLock(1, TimeUnit.SECONDS)) {
try {
if (lockB.tryLock(1, TimeUnit.SECONDS)) {
try { return true; }
finally { lockB.unlock(); }
}
} finally { lockA.unlock(); }
}
return false; // 포기 (회복)
}
@Service
public class DeadlockRecoveryStrategy {
// 데드락 감지 + 알림 (회복은 재시작)
@Scheduled(fixedRate = 30000)
public void monitorDeadlock() {
ThreadMXBean bean = ManagementFactory.getThreadMXBean();
long[] deadlocked = bean.findDeadlockedThreads();
if (deadlocked != null) {
alertService.sendCritical("데드락 감지! 재시작 필요");
// lock() 데드락은 자동 회복 불가
// → 운영자 개입 (재시작)
}
}
// 예방 — tryLock 으로 전환
private final ReentrantLock lock1 = new ReentrantLock();
private final ReentrantLock lock2 = new ReentrantLock();
public boolean safeProcess() throws InterruptedException {
if (lock1.tryLock(1, TimeUnit.SECONDS)) {
try {
if (lock2.tryLock(1, TimeUnit.SECONDS)) {
try {
doWork();
return true;
} finally {
lock2.unlock();
}
}
} finally {
lock1.unlock();
}
}
return false; // 데드락 회피
}
private void doWork() { }
}
lock()만 쓰는 시스템에서 데드락 회복 방법은?
답:
1. 회복 불가:
재시작:
사전 방지:
모니터링:
tryLock(5, SECONDS) 실패 후:
1. 재시도 (백오프)
- 잠시 후 다시
- 랜덤 백오프
2. 대체 경로
- 다른 방법으로
3. 작업 포기
- 사용자에게 알림
- 나중에
4. 큐에 적재
- 비동기 처리
// 백오프 재시도
public boolean processWithRetry(Shipment shipment) throws InterruptedException {
int maxRetries = 5;
long backoff = 100;
for (int i = 0; i < maxRetries; i++) {
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
doProcess(shipment);
return true;
} finally {
lock.unlock();
}
}
// 실패 → 백오프 후 재시도
Thread.sleep(backoff + ThreadLocalRandom.current().nextLong(50));
backoff *= 2; // 지수 백오프
}
return false; // 최종 실패
}
지수 백오프 (Exponential Backoff):
재시도마다 대기 시간 증가:
- 1차: 100ms
- 2차: 200ms
- 3차: 400ms
- ...
지터 (Jitter):
- 랜덤 추가
- 동시 재시도 분산
- 충돌 방지
backoff = base * 2^retry + random
// 대체 경로
public Result process(Shipment shipment) throws InterruptedException {
if (lock.tryLock(5, TimeUnit.SECONDS)) {
try {
return processFast(shipment); // 빠른 경로
} finally {
lock.unlock();
}
}
// 락 못 얻음 → 대체 경로
return processSlow(shipment); // 락 없는 느린 경로
}
private Result processFast(Shipment s) { return null; }
private Result processSlow(Shipment s) { return null; }
// 큐에 적재 (비동기)
public void process(Shipment shipment) throws InterruptedException {
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
doProcess(shipment);
} finally {
lock.unlock();
}
} else {
// 락 못 얻음 → 큐에 적재
deferredQueue.offer(shipment);
log.info("나중 처리 큐에 적재: {}", shipment.getId());
}
}
@Service
public class TryLockFailureStrategy {
private final ReentrantLock lock = new ReentrantLock();
private final BlockingQueue<Shipment> deferredQueue = new LinkedBlockingQueue<>();
public ProcessResult process(Shipment shipment) throws InterruptedException {
// 1. 즉시 시도 + 백오프 재시도
long backoff = 100;
for (int i = 0; i < 3; i++) {
if (lock.tryLock(2, TimeUnit.SECONDS)) {
try {
doProcess(shipment);
return ProcessResult.success();
} finally {
lock.unlock();
}
}
Thread.sleep(backoff + ThreadLocalRandom.current().nextLong(50));
backoff *= 2; // 지수 백오프
}
// 2. 재시도 실패 → 큐 적재 (대체 전략)
deferredQueue.offer(shipment);
log.warn("처리 지연: {} (큐 적재)", shipment.getId());
return ProcessResult.deferred();
}
private void doProcess(Shipment s) { }
record ProcessResult(String status) {
static ProcessResult success() { return new ProcessResult("SUCCESS"); }
static ProcessResult deferred() { return new ProcessResult("DEFERRED"); }
}
}
tryLock(5, SECONDS) 실패 후 전략은?
답:
1. 재시도:
대체 경로:
작업 포기:
큐 적재:
락 순서 일관성:
모든 스레드가 락을 항상 같은 순서로 획득.
효과:
- 순환 대기 불가
- 데드락 원천 방지
가장 실용적인 방법
// 락에 순서 부여 (예: id)
public void transferOrdered(Account from, Account to, int amount) {
// id 작은 것 먼저 (일관된 순서)
Account first = from.id < to.id ? from : to;
Account second = from.id < to.id ? to : from;
first.lock.lock(); // 항상 작은 id 먼저
try {
second.lock.lock();
try {
from.balance -= amount;
to.balance += amount;
} finally {
second.lock.unlock();
}
} finally {
first.lock.unlock();
}
}
// 모든 스레드가 id 순서 → 순환 X → 데드락 X
순서 일관성이 효과적인 이유:
순환 대기 (조건 4) 를 깸:
- 모두 같은 순서
- A→B 만 (B→A 없음)
- 순환 불가
예:
스레드 1: A→B
스레드 2: A→B (같은 순서)
→ 순환 X
→ 데드락 X
순서 일관:
스레드 1: A.lock()→B.lock() (A 먼저)
스레드 2: A.lock()→B.lock() (A 먼저, 같음)
- 스레드 1 이 A 보유 시
- 스레드 2 는 A 대기 (B 안 잡음)
- 1 이 A,B 처리 후 풀면
- 2 가 진행
→ 순환 X → 데드락 X
// id 없으면 해시 기반
public void transfer(Object lockA, Object lockB) {
int hashA = System.identityHashCode(lockA);
int hashB = System.identityHashCode(lockB);
Object first = hashA < hashB ? lockA : lockB;
Object second = hashA < hashB ? lockB : lockA;
synchronized (first) {
synchronized (second) {
// 일관된 순서
}
}
// 해시 같으면 (드물게) 타이브레이커 락 추가
}
@Service
public class LockOrderingConsistency {
// ✓ 락 순서 일관 (id 기반)
public void transferBalance(ShipmentAccount from, ShipmentAccount to,
BigDecimal amount) {
// 항상 id 작은 것 먼저 (순환 방지)
ShipmentAccount first = from.id < to.id ? from : to;
ShipmentAccount second = from.id < to.id ? to : from;
first.lock.lock();
try {
second.lock.lock();
try {
from.balance = from.balance.subtract(amount);
to.balance = to.balance.add(amount);
} finally {
second.lock.unlock();
}
} finally {
first.lock.unlock();
}
// 모든 스레드 같은 순서 → 데드락 X
}
static class ShipmentAccount {
final long id;
final ReentrantLock lock = new ReentrantLock();
BigDecimal balance = BigDecimal.ZERO;
ShipmentAccount(long id) { this.id = id; }
}
}
락 순서 일관성으로 데드락 방지는?
답:
1. 순서 일관:
방법:
효과:
실용성:
| Q | 핵심 답변 |
|---|---|
| tryLock 두 형태? | 즉시 / 타임아웃 |
| 데드락 4조건? | 상호배제/점유대기/비선점/순환대기 |
| 데드락 시나리오? | 락 순서 불일치 |
| lock() 데드락? | 영원히 (회복 불가) |
| tryLock 회피? | 포기/재시도 |
| 회복 방법? | 재시작 (lock()) |
| 실패 후 전략? | 백오프, 큐 |
| 락 순서 일관? | 순환 대기 깸 |
| 백오프? | 지수 + 지터 |
| 데드락 진단? | jstack, ThreadMXBean |
Q1. tryLock()? → 즉시 시도
Q2. tryLock(time)? → 타임아웃
Q3. 실패 시? → false
Q4. lock 과 차이? → 무한 vs 포기
Q5. 반환값? → boolean
Q6. 획득 후? → try-finally
Q7. 실패 후 unlock? → 안 함
Q8. InterruptedException? → tryLock(time)
Q9. 즉시 용도? → 중복 방지
Q10. 타임아웃 용도? → 데드락 회피
Q11. 백오프? → 재시도 간격
Q12. 지터? → 랜덤 분산
Q13. 4조건? → 상호배제/점유대기/비선점/순환대기
Q14. 상호 배제? → 한 번에 하나
Q15. 점유 대기? → 보유한 채 대기
Q16. 비선점? → 강제 회수 X
Q17. 순환 대기? → 서로 순환
Q18. 모두 충족? → 데드락
Q19. 방지? → 하나 깨기
Q20. 순환 깨기? → 락 순서
Q21. 비선점 깨기? → tryLock
Q22. 점유대기 깨기? → 전부 또는 포기
Q23. 가장 실용? → 순서 일관
Q24. 데드락 정의? → 서로 대기
Q25. 라이브락? → 계속 양보 (다름)
Q26. lock 데드락? → 영원히
Q27. 회복? → 재시작
Q28. tryLock 회피? → 양보/재시도
Q29. 양보? → 첫 락 풀기
Q30. 재시도? → 백오프
Q31. 타임아웃 회피? → tryLock(time)
Q32. 순환 깸? → 양보로
Q33. 회복 불가? → lock()
Q34. 회복 가능? → tryLock
Q35. 사전 방지? → 순서, tryLock
Q36. 진단? → jstack
Q37. ThreadMXBean? → findDeadlockedThreads
Q38. 감지 후? → 알림/재시작
Q39. 실패 전략? → 백오프, 큐
Q40. 지수 백오프? → 2배씩
Q41. 지터? → 랜덤
Q42. 대체 경로? → 락 없는 방법
Q43. 큐 적재? → 비동기
Q44. 락 순서? → 일관성
Q45. 순서 방법? → id, 해시
Q46. 순서 효과? → 순환 X
Q47. id 순서? → 작은 것 먼저
Q48. 해시 순서? → identityHashCode
Q49. 순서 vs tryLock? → 근본 vs 회피
Q50. 권장? → 순서 + tryLock
50 / 50 → tryLock/데드락 마스터
45-49 → 거의 마스터
40-44 → 복습
< 40 → Unit 5.4 재학습
Phase 5 — 정교한 락
Unit 5.1 — synchronized의 한계
- 무한 대기, 인터럽트 X, 공정성 X
Unit 5.2 — LockSupport
- park/unpark
- 저수준, 고수준 기반
Unit 5.3 — ReentrantLock
- lock/unlock, try-finally
- 재진입, 공정성, Condition
Unit 5.4 — tryLock (★ 마스터)
- 데드락 회피
- 락 순서 일관
Phase 5 핵심 통찰 5가지:
1. synchronized 한계
- 무한 대기, 인터럽트 X
2. ReentrantLock
- 정교한 제어
- try-finally 필수
3. tryLock
- 포기/재시도
- 데드락 회피
4. 데드락 4조건
- 순환 대기 깨기 (락 순서)
5. 실무
- 락 순서 일관 + tryLock
✅ Phase 1 — 동시성의 기초 (4 Unit)
✅ Phase 2 — 4분면 매트릭스 (3 Unit)
✅ Phase 3 — 스레드 다루기 (5 Unit)
✅ Phase 4 — synchronized & volatile (5 Unit) ★ 1차 정점
✅ Phase 5 — Lock 도구 (4 Unit) ← 완주
⏭ Phase 6 — 스레드 협력 (4 Unit)
⏭ Phase 7 — Executor (7 Unit) ★ 2차 정점
⏭ Phase 8 — 고급 비동기 (3 Unit)
총: 21/35 Unit (Phase 5 완주, 약 60%)
답:
답:
답:
답:
답:
1. tryLock
2. 데드락
3. 방지
🚀 Phase 5 — 정교한 락
✅ Unit 5.1 synchronized의 한계
✅ Unit 5.2 LockSupport
✅ Unit 5.3 ReentrantLock
✅ Unit 5.4 tryLock (★ 마스터) ← 여기, Phase 5 완주
→ synchronized 한계 → ReentrantLock
→ tryLock 데드락 회피
→ 락 순서 일관성
Phase 6 — 스레드 협력 (4 Unit)
Unit 6.1 — 생산자-소비자 문제
Unit 6.2 — wait()과 notify()
Unit 6.3 — 인터럽트(Interrupt) 메커니즘
Unit 6.4 — yield()
✅ Phase 1~5 (21 Unit, 1차 정점 완료)
⏭ Phase 6 — 스레드 협력 (4 Unit)
총: 21/35 Unit (약 60%)
★ 마스터 Unit — tryLock 데드락 회피 완료
🏆 Phase 5 완주 — Lock 도구 마스터