Day21--锁机制和死锁概述(公平锁、非公平锁、乐观锁、悲观锁以及死锁的综合介绍)

在并发编程中,锁是用于控制对共享资源访问的机制。理解不同类型的锁及其特点是实现高效和可靠多线程程序的基础。以下是公平锁、非公平锁、乐观锁、悲观锁的介绍以及如何处理死锁的讨论。

公平锁与非公平锁

公平锁

定义: 公平锁是一种确保线程按照请求的顺序获得锁的机制。它保证了线程在请求锁时的顺序性,通常通过维护一个队列来实现。

特点:

  • 公平性: 保证了线程获得锁的顺序与请求的顺序一致,避免了“饥饿”现象。
  • 性能开销: 由于需要维护线程队列,可能导致更多的性能开销和线程上下文切换。
  • 应用场景: 适用于需要严格保证线程执行顺序的场景,如金融系统、任务调度等。

示例(Java):

ReentrantLock lock = new ReentrantLock(true); // 公平锁
非公平锁

定义: 非公平锁不保证线程按照请求的顺序获得锁,它允许线程在锁释放时“插队”,尝试先获得锁。

特点:

  • 性能: 通常具有较好的性能,能够减少锁竞争带来的开销。
  • 公平性差: 可能导致某些线程长期无法获得锁,即线程“饥饿”。
  • 应用场景: 适用于对性能要求较高且不需要严格公平性的场景,如高性能计算任务。

示例(Java):

ReentrantLock lock = new ReentrantLock(false); // 非公平锁

乐观锁与悲观锁

乐观锁

定义: 乐观锁是一种基于版本机制的锁策略,它假设并发冲突不会频繁发生,因此在操作前不会加锁,而是在提交时检查是否有其他操作修改了数据。

特点:

  • 无锁操作: 减少了锁竞争带来的开销,适合读多写少的场景。
  • 冲突重试: 如果检测到冲突,操作将会重试,可能会导致性能下降。
  • 应用场景: 适用于读操作远多于写操作的场景,如缓存系统、某些数据库操作。

示例:
在数据库中,乐观锁通常通过版本号实现。例如,更新时检查记录的版本号是否一致,如果一致则更新成功,否则重试。

悲观锁

定义: 悲观锁是一种假设并发冲突频繁的锁策略,因此在操作之前会加锁,确保对共享资源的独占访问。

特点:

  • 数据一致性: 通过加锁来避免并发冲突,适用于写操作频繁的场景。
  • 锁竞争: 可能导致较高的锁竞争,影响性能。
  • 应用场景: 适用于写操作多且冲突较多的场景,如银行转账、事务处理等。

示例:
在数据库中,悲观锁通过“锁表”或“锁行”实现。例如,使用 SELECT ... FOR UPDATE 语句来锁定记录,直到事务结束。

死锁

定义: 死锁是指多个进程(或线程)在相互等待对方释放资源时形成的僵局状态,导致所有参与的进程(或线程)都无法继续执行。

死锁的四个条件:

  1. 互斥条件: 至少有一个资源必须处于排他模式,不能被多个进程(或线程)共享。
  2. 占有并等待: 已持有资源的进程在等待其他资源时,保持已持有的资源不释放。
  3. 不剥夺条件: 已获得的资源不能被强行剥夺,只能由持有者自愿释放。
  4. 循环等待: 存在一种进程(或线程)循环等待的关系,即进程 (P1) 等待进程 (P2) 持有的资源,进程 (P2) 等待进程 (P3) 持有的资源,以此类推,最终进程 (Pn) 等待进程 (P1) 持有的资源。

死锁的预防与避免:

  1. 死锁预防: 通过破坏死锁的四个条件之一来防止死锁的发生。

    • 破坏互斥条件(共享资源)。
    • 破坏占有并等待条件(一次性请求所有资源)。
    • 破坏不剥夺条件(强行剥夺资源)。
    • 破坏循环等待条件(资源排序)。
  2. 死锁避免: 使用算法(如银行家算法)动态检查资源分配是否会导致死锁。避免进入不安全状态。

  3. 死锁检测与恢复: 允许系统进入死锁状态,但实时检测并采取措施恢复。

    • 资源分配图:用于检测死锁状态。
    • 进程回滚:将进程回滚到某个安全状态,释放资源。
    • 进程终止:终止一个或多个进程以解除死锁。

示例(Java):

public class DeadlockExample {
    private final Object lock1 = new Object();
    private final Object lock2 = new Object();

    public void method1() {
        synchronized (lock1) {
            System.out.println("Holding lock1...");
            try { Thread.sleep(100); } catch (InterruptedException e) {}
            synchronized (lock2) {
                System.out.println("Holding lock1 and lock2...");
            }
        }
    }

    public void method2() {
        synchronized (lock2) {
            System.out.println("Holding lock2...");
            try { Thread.sleep(100); } catch (InterruptedException e) {}
            synchronized (lock1) {
                System.out.println("Holding lock2 and lock1...");
            }
        }
    }
}

在这个例子中,如果两个线程分别执行 method1()method2(),就会导致死锁,因为每个线程都在等待另一个线程释放其持有的锁。

  • 35
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IT ·南栀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值