Chuyển tới nội dung chính

Locks và Conditions

Mục tiêu bài học

Sau bài này, bạn sẽ:

  • Hiểu được sự khác biệt giữa ReentrantLock và synchronized
  • Sử dụng được tryLock() để tránh deadlock
  • Nắm được Condition interface và cách dùng thay thế wait/notify
  • Biết cách dùng ReadWriteLock cho read-heavy workloads
  • Hiểu được StampedLock và optimistic locking

Bài trước: Concurrent Collections — Đã học về ConcurrentHashMap và BlockingQueue. Bài này sẽ giới thiệu các lock mechanisms linh hoạt hơn synchronized.

Tại sao cần Lock riêng biệt?

Từ Java 1.0, synchronized là công cụ chính để đồng bộ threads. Tuy nhiên, synchronized có nhiều hạn chế:

  1. Không thể "thử" acquire lock: Nếu lock đang bị giữ, thread bị block vô thời hạn — không có cách nào để "thử lock rồi làm việc khác nếu không được"
  2. Không có fairness policy: synchronizedunfair by default và không thể thay đổi — threads có thể bị starvation
  3. Chỉ có một wait set: Mỗi object monitor chỉ có một wait set duy nhất — không thể có nhiều conditions riêng biệt (ví dụ: "queue not full" và "queue not empty")
  4. Không interruptible: Thread đang chờ synchronized lock không thể bị interrupt — không thể cancel operation
  5. Must release trong cùng block: Phải acquire và release lock trong cùng một block/method — không thể tách acquire ở method này, release ở method khác

Package java.util.concurrent.locks giải quyết TẤT CẢ những hạn chế này:

// synchronized: Không thể tryLock
synchronized (lock) {
// Bị block vô thời hạn nếu lock đang bị giữ
}

// Lock: Có thể tryLock
if (lock.tryLock()) {
try {
// Acquired!
} finally {
lock.unlock();
}
} else {
// Không acquire được, làm việc khác
}
Khi nào nên dùng Lock thay vì synchronized?
  • Cần tryLock() để tránh blocking/deadlock
  • Cần timeout khi acquire lock
  • Cần interruptible locking (có thể cancel)
  • Cần fairness (FIFO ordering)
  • Cần nhiều Condition variables (nhiều wait sets)

java.util.concurrent.locks Package

Package java.util.concurrent.locks cung cấp các lock mechanisms linh hoạt hơn synchronized:

import java.util.concurrent.locks.*;

Interfaces chính:

  • Lock: Interface cơ bản
  • ReadWriteLock: Separate read/write locks
  • Condition: Alternative cho wait/notify

Implementations:

  • ReentrantLock: Lock có thể reentrant
  • ReentrantReadWriteLock: ReadWriteLock implementation
  • StampedLock: Optimistic reading (Java 8+)

ReentrantLock

ReentrantLock là lock có thể reentrant (thread đang giữ lock có thể acquire lại):

Reentrant có nghĩa là gì?

Reentrant = "có thể vào lại" — thread đã giữ lock có thể acquire lại lock đókhông bị deadlock:

Lock lock = new ReentrantLock();

public void method1() {
lock.lock(); // Acquire lần 1: hold count = 1
try {
System.out.println("Method 1");
method2(); // Gọi method2, cũng cần lock
} finally {
lock.unlock(); // Release: hold count = 0
}
}

public void method2() {
lock.lock(); // Acquire lần 2 (cùng thread): hold count = 2
try {
System.out.println("Method 2");
} finally {
lock.unlock(); // Release: hold count = 1
}
}

Cơ chế:

  • Lock lưu hold count (số lần lock đã được acquire)
  • Mỗi lần lock(): hold count tăng 1
  • Mỗi lần unlock(): hold count giảm 1
  • Lock chỉ được release khi hold count = 0
Quan trọng
  • Số lần unlock() phải bằng số lần lock() — nếu không sẽ không bao giờ release lock (deadlock)!
  • Cả synchronizedReentrantLock đều reentrant — synchronized cũng cho phép thread acquire lại monitor của object

Ví dụ synchronized reentrant:

public synchronized void method1() {
System.out.println("Method 1");
method2(); // Cùng thread acquire monitor lại
}

public synchronized void method2() {
System.out.println("Method 2"); // OK, không deadlock
}

Cấu trúc cơ bản

import java.util.concurrent.locks.*;

public class ReentrantLockDemo {
private final Lock lock = new ReentrantLock();
private int count = 0;

public void increment() {
lock.lock(); // Acquire lock
try {
count++;
System.out.println(Thread.currentThread().getName() +
" incremented to " + count);
} finally {
lock.unlock(); // LUÔN unlock trong finally
}
}

public int getCount() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
}
Quan trọng
  • LUÔN gọi unlock() trong finally block để đảm bảo unlock ngay cả khi có exception
  • Số lần lock() phải bằng unlock(): Không unlock → deadlock!

tryLock() - Non-blocking Lock

import java.util.concurrent.locks.*;
import java.util.concurrent.TimeUnit;

public class TryLockDemo {
private final Lock lock = new ReentrantLock();

public void method1() {
// Thử acquire lock, không chờ
if (lock.tryLock()) {
try {
System.out.println("Lock acquired by " +
Thread.currentThread().getName());
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
} else {
System.out.println("Could not acquire lock, doing other work");
}
}

public void method2() throws InterruptedException {
// Thử acquire lock với timeout
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
System.out.println("Lock acquired with timeout");
} finally {
lock.unlock();
}
} else {
System.out.println("Timeout! Could not acquire lock");
}
}

public static void main(String[] args) {
TryLockDemo demo = new TryLockDemo();

Thread t1 = new Thread(() -> demo.method1(), "Thread-1");
Thread t2 = new Thread(() -> demo.method1(), "Thread-2");

t1.start();
try { Thread.sleep(100); } catch (InterruptedException e) {}
t2.start();
}
}

Output:

Lock acquired by Thread-1
Could not acquire lock, doing other work

lockInterruptibly() - Interruptible Lock

public void interruptibleMethod() throws InterruptedException {
lock.lockInterruptibly(); // Có thể bị interrupt trong khi chờ lock
try {
// Critical section
while (!Thread.interrupted()) {
// Do work
}
} finally {
lock.unlock();
}
}

Fair Lock

// Fair lock: Thread chờ lâu nhất được ưu tiên (FIFO)
Lock fairLock = new ReentrantLock(true);

// Unfair lock (default): Không đảm bảo thứ tự, nhưng nhanh hơn
Lock unfairLock = new ReentrantLock(false);
mẹo
  • Fair lock: Tránh starvation, nhưng throughput thấp hơn
  • Unfair lock: Throughput cao hơn, nhưng có thể xảy ra starvation

ReentrantLock vs synchronized

FeatureReentrantLocksynchronized
APIExplicit lock/unlockImplicit (enter/exit block)
try-lockCó (tryLock())Không
TimeoutCó (tryLock(time))Không
InterruptibleCó (lockInterruptibly())Không
FairnessCó (optional)Không
Condition variablesNhiều Conditions1 monitor (wait/notify)
PerformanceTương đươngTương đương
ComplexityPhức tạp hơnĐơn giản hơn
Forget unlockCó thể quên → deadlockTự động unlock

Khi nào dùng ReentrantLock?

Dùng ReentrantLock khi:

  • Cần tryLock() để tránh deadlock
  • Cần timeout khi acquire lock
  • Cần interruptible locking
  • Cần fairness
  • Cần nhiều Condition variables

Dùng synchronized khi:

  • Đơn giản, không cần features nâng cao
  • Không muốn lo lắng quên unlock
  • Code dễ đọc hơn

Condition Interface

Condition là alternative cho wait(), notify(), notifyAll():

public interface Condition {
void await() throws InterruptedException; // Như wait()
boolean await(long time, TimeUnit unit); // wait() với timeout
void signal(); // Như notify()
void signalAll(); // Như notifyAll()
}

Ví dụ: Producer-Consumer với Condition

import java.util.concurrent.locks.*;
import java.util.LinkedList;
import java.util.Queue;

public class ProducerConsumerCondition {
private final Queue<Integer> queue = new LinkedList<>();
private final int CAPACITY = 5;

private final Lock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition(); // Queue chưa đầy
private final Condition notEmpty = lock.newCondition(); // Queue chưa rỗng

public void produce(int value) throws InterruptedException {
lock.lock();
try {
// Chờ cho đến khi queue chưa đầy
while (queue.size() == CAPACITY) {
System.out.println("Queue full. Producer waiting...");
notFull.await();
}

queue.add(value);
System.out.println("Produced: " + value + " | Queue size: " + queue.size());

// Thông báo cho consumers
notEmpty.signal();
} finally {
lock.unlock();
}
}

public int consume() throws InterruptedException {
lock.lock();
try {
// Chờ cho đến khi queue chưa rỗng
while (queue.isEmpty()) {
System.out.println("Queue empty. Consumer waiting...");
notEmpty.await();
}

int value = queue.poll();
System.out.println("Consumed: " + value + " | Queue size: " + queue.size());

// Thông báo cho producers
notFull.signal();

return value;
} finally {
lock.unlock();
}
}

public static void main(String[] args) {
ProducerConsumerCondition pc = new ProducerConsumerCondition();

// Producer thread
Thread producer = new Thread(() -> {
try {
for (int i = 1; i <= 10; i++) {
pc.produce(i);
Thread.sleep(500);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});

// Consumer thread
Thread consumer = new Thread(() -> {
try {
for (int i = 1; i <= 10; i++) {
pc.consume();
Thread.sleep(1000); // Consumer chậm hơn
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});

producer.start();
consumer.start();
}
}

Spurious Wakeup và Tại sao phải dùng while

Spurious wakeup = "đánh thức giả" — await() có thể return mà KHÔNG có signal() nào được gọi!

// SAI - Dùng if
lock.lock();
try {
if (queue.isEmpty()) { // ❌ KHÔNG dùng if
notEmpty.await();
}
// Có thể spurious wakeup → queue vẫn empty!
queue.poll(); // NullPointerException!
} finally {
lock.unlock();
}

// ĐÚNG - Dùng while
lock.lock();
try {
while (queue.isEmpty()) { // ✅ Luôn dùng while
notEmpty.await();
}
// Guarantee: queue không empty
queue.poll(); // Safe
} finally {
lock.unlock();
}

Tại sao xảy ra spurious wakeup?

  • Hệ điều hành có thể đánh thức thread bất cứ lúc nào (OS-level optimization)
  • JVM không đảm bảo await() chỉ return khi có signal()
  • Đây là behavior hợp lệ theo Java specification

Quy tắc:

// LUÔN LUÔN dùng while loop
while (!condition) {
conditionVariable.await();
}
mẹo

Spurious wakeup xảy ra với cả Object.wait()Condition.await() — luôn dùng while loop cho cả hai!

// Object.wait() - cũng cần while
synchronized (lock) {
while (!condition) {
lock.wait(); // Spurious wakeup có thể xảy ra
}
}

Ưu điểm của Condition

mẹo
  1. Nhiều Conditions: Một Lock có thể có nhiều Conditions riêng biệt

    Condition notFull = lock.newCondition();
    Condition notEmpty = lock.newCondition();
    Condition customCondition = lock.newCondition();
  2. Rõ ràng hơn wait/notify: Mỗi condition có ý nghĩa cụ thể

    notEmpty.signal();  // Rõ ràng: signal "queue not empty"
    lock.notify(); // Không rõ: notify cái gì?
  3. Interruptible: await() có thể bị interrupt

ReadWriteLock

ReadWriteLock cho phép:

  • Nhiều readers đồng thời (shared lock)
  • Chỉ 1 writer (exclusive lock)
  • Writer block tất cả readers và writers
Read Lock (Shared)
Thread 1: ████████ (reading)
Thread 2: ████████ (reading) ← Nhiều readers cùng lúc
Thread 3: ████████ (reading)

Write Lock (Exclusive)
Thread 4: ░░░░████████░░░░ (writing) ← Chỉ 1 writer
↑ ↑
Block readers Release

Khi nào dùng ReadWriteLock?

Dùng khi:

  • Đọc nhiều hơn ghi (read-heavy workload)
  • Nhiều threads đọc cùng data
  • Write operations ít xảy ra

Không dùng khi:

  • Write nhiều (overhead không đáng)
  • Cần simplicity

ReentrantReadWriteLock

import java.util.concurrent.locks.*;

public class ReentrantReadWriteLockDemo {
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = rwLock.writeLock();

private int value = 0;

// Multiple readers can execute this concurrently
public int read() {
readLock.lock();
try {
System.out.println(Thread.currentThread().getName() +
" reading: " + value);
Thread.sleep(1000); // Giả lập read operation
return value;
} catch (InterruptedException e) {
e.printStackTrace();
return -1;
} finally {
readLock.unlock();
}
}

// Only one writer can execute this
public void write(int newValue) {
writeLock.lock();
try {
System.out.println(Thread.currentThread().getName() +
" writing: " + newValue);
value = newValue;
Thread.sleep(1000); // Giả lập write operation
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
writeLock.unlock();
}
}

public static void main(String[] args) {
ReentrantReadWriteLockDemo demo = new ReentrantReadWriteLockDemo();

// 5 reader threads
for (int i = 1; i <= 5; i++) {
new Thread(() -> demo.read(), "Reader-" + i).start();
}

// 1 writer thread
new Thread(() -> demo.write(100), "Writer-1").start();

// More readers
try { Thread.sleep(500); } catch (InterruptedException e) {}
for (int i = 6; i <= 8; i++) {
new Thread(() -> demo.read(), "Reader-" + i).start();
}
}
}

Output:

Reader-1 reading: 0
Reader-2 reading: 0 ← Readers chạy song song
Reader-3 reading: 0
Reader-4 reading: 0
Reader-5 reading: 0
Writer-1 writing: 100 ← Writer chờ readers xong
Reader-6 reading: 100 ← Readers tiếp tục sau writer
Reader-7 reading: 100
Reader-8 reading: 100

Ví dụ thực tế: Read-Heavy Cache

import java.util.concurrent.locks.*;
import java.util.*;

public class ReadHeavyCache {
private final Map<String, String> cache = new HashMap<>();
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = rwLock.writeLock();

// Read operation (nhiều threads có thể đọc cùng lúc)
public String get(String key) {
readLock.lock();
try {
System.out.println(Thread.currentThread().getName() +
" reading key: " + key);
simulateWork(100); // Giả lập read từ cache
return cache.get(key);
} finally {
readLock.unlock();
}
}

// Write operation (chỉ 1 thread)
public void put(String key, String value) {
writeLock.lock();
try {
System.out.println(Thread.currentThread().getName() +
" writing key: " + key);
simulateWork(500); // Giả lập expensive operation
cache.put(key, value);
} finally {
writeLock.unlock();
}
}

// Upgrade lock (KHÔNG ĐƯỢC HỖ TRỢ TRỰC TIẾP)
public void updateIfExists(String key, String newValue) {
readLock.lock();
try {
if (cache.containsKey(key)) {
// PHẢI release read lock trước khi acquire write lock
readLock.unlock();
writeLock.lock();
try {
// Double-check
if (cache.containsKey(key)) {
cache.put(key, newValue);
}
} finally {
// Downgrade: acquire read lock trước khi release write lock
readLock.lock();
writeLock.unlock();
}
}
} finally {
readLock.unlock();
}
}

private void simulateWork(int ms) {
try { Thread.sleep(ms); } catch (InterruptedException e) {}
}

public static void main(String[] args) throws InterruptedException {
ReadHeavyCache cache = new ReadHeavyCache();

// Initialize cache
cache.put("user:1", "Alice");
cache.put("user:2", "Bob");

// 10 reader threads
for (int i = 1; i <= 10; i++) {
final int id = i % 2 + 1;
new Thread(() -> {
for (int j = 0; j < 3; j++) {
String value = cache.get("user:" + id);
System.out.println(" Got: " + value);
}
}, "Reader-" + i).start();
}

// 2 writer threads
Thread.sleep(1000);
new Thread(() -> cache.put("user:3", "Charlie"), "Writer-1").start();
new Thread(() -> cache.put("user:4", "David"), "Writer-2").start();
}
}
Lock Upgrade

ReadWriteLock KHÔNG hỗ trợ lock upgrade trực tiếp (read → write). Phải:

  1. Release read lock
  2. Acquire write lock
  3. Double-check condition
  4. (Optional) Downgrade: acquire read lock trước khi release write lock

StampedLock (Java 8+)

StampedLock là lock cao cấp hơn với optimistic reading:

StampedLock KHÔNG reentrant!

StampedLock KHÔNG reentrant — nếu cùng thread cố gắng acquire lại lock, sẽ bị DEADLOCK!

StampedLock lock = new StampedLock();

public void method1() {
long stamp = lock.writeLock(); // Acquire
try {
method2(); // Gọi method2
} finally {
lock.unlockWrite(stamp);
}
}

public void method2() {
long stamp = lock.writeLock(); // DEADLOCK! Same thread
try {
// Never executes
} finally {
lock.unlockWrite(stamp);
}
}

Lý do: StampedLock được thiết kế cho maximum performance ở read-heavy workloads, không hỗ trợ reentrancy để giảm overhead.

Khác biệt:

  • ReentrantLock: Reentrant
  • ReentrantReadWriteLock: Reentrant
  • StampedLock: KHÔNG reentrant

3 modes

  1. Write Lock: Exclusive lock (như write lock của ReadWriteLock)
  2. Read Lock: Shared lock (như read lock của ReadWriteLock)
  3. Optimistic Read: Không lock, chỉ kiểm tra version
import java.util.concurrent.locks.*;

public class StampedLockDemo {
private final StampedLock lock = new StampedLock();
private double x = 0, y = 0;

// Write
public void move(double deltaX, double deltaY) {
long stamp = lock.writeLock(); // Exclusive lock
try {
x += deltaX;
y += deltaY;
} finally {
lock.unlockWrite(stamp);
}
}

// Optimistic Read
public double distanceFromOrigin() {
long stamp = lock.tryOptimisticRead(); // Không lock!
double currentX = x;
double currentY = y;

if (!lock.validate(stamp)) { // Kiểm tra có bị modify không
// Nếu có write xảy ra, upgrade lên read lock
stamp = lock.readLock();
try {
currentX = x;
currentY = y;
} finally {
lock.unlockRead(stamp);
}
}

return Math.sqrt(currentX * currentX + currentY * currentY);
}

// Pessimistic Read
public double getX() {
long stamp = lock.readLock();
try {
return x;
} finally {
lock.unlockRead(stamp);
}
}

// Convert read lock to write lock
public void moveIfAtOrigin(double deltaX, double deltaY) {
long stamp = lock.readLock();
try {
while (x == 0.0 && y == 0.0) {
// Thử convert read lock → write lock
long writeStamp = lock.tryConvertToWriteLock(stamp);
if (writeStamp != 0L) { // Conversion thành công
stamp = writeStamp;
x = deltaX;
y = deltaY;
break;
} else { // Conversion thất bại
lock.unlockRead(stamp);
stamp = lock.writeLock(); // Acquire write lock
}
}
} finally {
lock.unlock(stamp);
}
}

public static void main(String[] args) {
StampedLockDemo demo = new StampedLockDemo();

// Writers
for (int i = 0; i < 5; i++) {
final int id = i;
new Thread(() -> {
demo.move(id, id * 2);
System.out.println("Moved to (" + demo.getX() + ", " + demo.y + ")");
}, "Writer-" + i).start();
}

// Readers (optimistic)
for (int i = 0; i < 10; i++) {
new Thread(() -> {
double distance = demo.distanceFromOrigin();
System.out.println("Distance: " + distance);
}, "Reader-" + i).start();
}
}
}

Optimistic Read Flow

1. stamp = tryOptimisticRead()  // Không lock, lấy version
2. Đọc dữ liệu // Có thể bị concurrent write
3. validate(stamp) // Kiểm tra version có thay đổi không?
- Nếu valid: Dùng dữ liệu đã đọc
- Nếu invalid: Upgrade lên read lock và đọc lại
Khi nào dùng StampedLock?

Dùng khi:

  • Read RẤT NHIỀU hơn write (95%+ reads)
  • Muốn tối ưu performance tối đa
  • Có thể chấp nhận complexity cao

Không dùng khi:

  • Cần reentrancy (StampedLock không reentrant)
  • Code cần đơn giản, dễ maintain
  • Write operations nhiều

StampedLock vs ReentrantReadWriteLock

FeatureStampedLockReentrantReadWriteLock
Optimistic readKhông
Performance (read-heavy)Tốt hơnTốt
ReentrancyKhông
ComplexityCaoMedium
Lock conversionKhông

Best Practices

mẹo
  1. Luôn unlock trong finally

    lock.lock();
    try {
    // Critical section
    } finally {
    lock.unlock(); // Đảm bảo unlock
    }
  2. Dùng tryLock để tránh deadlock

    if (lock.tryLock(100, TimeUnit.MILLISECONDS)) {
    try {
    // Work
    } finally {
    lock.unlock();
    }
    } else {
    // Handle failure
    }
  3. await() trong while loop

    while (condition) {
    condition.await(); // Không dùng if
    }
  4. Choose appropriate lock:

    • Simple: synchronized
    • Advanced features: ReentrantLock
    • Read-heavy: ReentrantReadWriteLock
    • Read-very-heavy: StampedLock
  5. Fair locks cho critical fairness

    Lock fairLock = new ReentrantLock(true);
  6. Avoid lock upgrade: Release read, acquire write

Bài tập

Bài 1: Thread-Safe Counter với tryLock

Implement counter:

  • Dùng tryLock() để tránh blocking
  • Nếu không acquire được lock, retry hoặc skip
  • Track số lần failed acquisitions

Bài 2: Read-Write Cache

Implement cache với ReadWriteLock:

  • get(key): Read lock
  • put(key, value): Write lock
  • computeIfAbsent(key, function): Lock upgrade pattern
  • Benchmark với 90% reads, 10% writes

Bài 3: Bounded Buffer với Conditions

Implement bounded buffer:

  • put(item): Chờ nếu full (Condition notFull)
  • take(): Chờ nếu empty (Condition notEmpty)
  • size(): Return current size
  • Test với multiple producers/consumers

Bài 4: Optimistic Locking

Implement counter với StampedLock optimistic read:

  • increment(): Write lock
  • get(): Optimistic read
  • Measure performance vs ReentrantLock
OCP Exam Tips

Các điểm quan trọng cho thi OCP:

  1. Lock phải unlock trong finally block

    lock.lock();
    try {
    // Critical section
    } finally {
    lock.unlock(); // BẮT BUỘC trong finally
    }

    Pattern này là BẮT BUỘC — nếu không có finally, có thể leak lock khi có exception!

  2. ReentrantLock is reentrant, StampedLock is NOT

    • ReentrantLock: Thread có thể acquire lại ✅
    • ReentrantReadWriteLock: Thread có thể acquire lại ✅
    • StampedLock: DEADLOCK nếu acquire lại ❌
  3. ReadWriteLock: nhiều readers HOẶC single writer

    // Có thể:
    Reader 1 + Reader 2 + Reader 3
    Writer 1 (alone)

    // KHÔNG thể:
    Reader 1 + Writer 1
    Writer 1 + Writer 2
  4. tryLock() vs tryLock(timeout)

    tryLock()           // Return immediately
    tryLock(1, SECONDS) // Wait up to 1 second
  5. Condition.await() releases lock

    • await() release lock (giống Object.wait())
    • Thread khác có thể acquire lock
    • signal() wake thread nhưng không release lock — thread được wake vẫn phải chờ lock
  6. Fair lock: new ReentrantLock(true)

    Lock fairLock = new ReentrantLock(true);    // FIFO ordering
    Lock unfairLock = new ReentrantLock(false); // Default
  7. lock() vs lockInterruptibly()

    lock.lock();               // KHÔNG thể interrupt
    lock.lockInterruptibly(); // Có thể interrupt

    Nếu thread đang chờ lockInterruptibly(), có thể bị interrupt() → throw InterruptedException.

Tóm tắt

  • ReentrantLock: Lock linh hoạt với tryLock, timeout, interruptible
  • Condition: Alternative cho wait/notify, nhiều conditions per lock
  • ReadWriteLock: Nhiều readers đồng thời, 1 writer
  • StampedLock: Optimistic reading cho read-very-heavy workloads
  • Best practice: Luôn unlock trong finally, chọn lock phù hợp

Bài tiếp theo: Multithreading Best Practices - Tổng hợp best practices và common pitfalls!

Đọc thêm