悲观锁和乐观锁

在Java中,悲观锁和乐观锁是处理并发访问共享资源时采用的不同策略。它们主要的区别在于对数据竞争的预期和处理方式。

悲观锁 (Pessimistic Lock)

悲观锁基于“悲观”的假设,即默认情况下它认为数据可能会被其他线程修改,因此在操作数据前会尝试获得独占的锁。一旦某个线程持有悲观锁,其他试图访问相同资源的线程将被阻塞,直到锁被释放。

概念和作用:
悲观锁通过确保在任何时刻只有一个线程能够访问共享资源,从而避免了数据的竞争条件。它保证了数据的一致性和完整性,但是可能会降低系统的整体性能,因为等待锁的线程会被阻塞。

示例代码:

public class PessimisticLockExample {
    public static void main(String[] args) {
        Object lock = new Object();
    
        Thread thread1 = new Thread(() -> {
            synchronized (lock) { // 悲观锁
                System.out.println("Thread 1: Acquired lock");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Thread 1: Releasing lock");
            }
        });
    
        Thread thread2 = new Thread(() -> {
            synchronized (lock) { // 悲观锁
                System.out.println("Thread 2: Attempting to acquire lock");
            }
        });
    
        thread1.start();
        thread2.start();
    }
}

在这个例子中,thread2​在thread1​释放锁之前将被阻塞,无法执行其代码。

乐观锁 (Optimistic Lock)

乐观锁基于“乐观”的假设,即默认情况下它认为数据不会被其他线程修改,因此在操作数据时不立即加锁。当需要更新数据时,它会检查在此期间数据是否已经被其他线程修改过。如果数据未被修改,则更新成功;如果数据已被修改,则更新失败,并可能触发重试。

概念和作用:
乐观锁主要用于减少锁带来的性能开销,尤其是在读操作远多于写操作的场景下。它通过避免不必要的锁争用来提高并发性能,但这也意味着在数据确实被其他线程修改时,操作可能会失败。

实现方式:
乐观锁可以通过版本号机制或CAS(Compare and Swap)算法来实现。

示例代码:

import java.util.concurrent.atomic.AtomicInteger;

public class OptimisticLockExample {
    public static void main(String[] args) {
        AtomicInteger counter = new AtomicInteger(0); // 使用乐观锁
    
        Thread thread1 = new Thread(() -> {
            int oldValue = counter.get();
            // 假设这里有一段长的计算过程...
            int newValue = oldValue + 1;
            boolean success = counter.compareAndSet(oldValue, newValue); // CAS操作
            if (success) {
                System.out.println("Thread 1: Incremented value to " + newValue);
            } else {
                System.out.println("Thread 1: Failed to increment value.");
            }
        });
    
        Thread thread2 = new Thread(() -> {
            int oldValue = counter.get();
            // 假设这里有一段长的计算过程...
            int newValue = oldValue + 1;
            boolean success = counter.compareAndSet(oldValue, newValue); // CAS操作
            if (success) {
                System.out.println("Thread 2: Incremented value to " + newValue);
            } else {
                System.out.println("Thread 2: Failed to increment value.");
            }
        });
    
        thread1.start();
        thread2.start();
    }
}

在这个例子中,AtomicInteger​的compareAndSet​方法是一个CAS操作,它实现了乐观锁。如果两个线程同时尝试增加计数器的值,只有一个线程的更新会成功,另一个线程的更新会失败,因为它检测到数据已经发生了变化。

posted @ 2024-07-07 09:37  捞月亮的小北  阅读(14)  评论(0编辑  收藏  举报