悲观锁:悲观锁的思想是在访问共享资源之前,先假设其他线程会对其进行修改,因此采取加锁的方式来保护资源的完整性。这意味着在使用悲观锁时,线程在进入临界区之前会查看是否有其他线程已经获取了锁,如果有,则会等待直到锁被释放。在 Java 中,synchronized 关键字和 ReentrantLock 类都是实现悲观锁的常用手段。
悲观锁的工作流程如下:
- 线程想要访问共享资源之前,先获取锁。
- 如果资源已经被其他线程获取,则当前线程等待锁的释放。
- 如果资源未被其他线程获取,则当前线程获取锁,进入临界区,执行操作。
- 当线程完成操作后,释放锁,其他线程可以继续竞争获取锁。
悲观锁适用于以下情况:
- 数据一致性要求较高,对并发冲突的处理需要保证操作的原子性。
- 并发冲突概率较高,争用资源的线程较多。
- 必须防止数据竞争或冲突修改。
乐观锁:乐观锁的思想是假设多个线程之间的并发访问不会导致冲突,因此在操作之前不加锁,仅在更新时进行冲突检测。乐观锁通过版本号或时间戳等机制来识别是否发生了冲突。在 Java 中,可以使用 AtomicInteger 或 AtomicReference 来表示版本号,通过比较版本号来判断是否发生冲突。
乐观锁的工作流程如下:
- 线程想要访问共享资源之前,先记录当前版本号。
- 线程完成操作后,检查当前版本号是否和之前记录的版本号一致。
- 如果一致,则说明没有发生冲突,操作成功。
- 如果版本号不一致,则说明有其他线程对资源进行了修改,当前线程需要处理冲突,可能需要重试或执行其他逻辑。
乐观锁适用于以下情况:
- 并发冲突概率较低,争用资源的线程较少。
- 对性能要求较高,不希望频繁地加锁和解锁。
- 出现冲突时,可以通过重试操作或其他逻辑来处理。
需要注意的是,悲观锁和乐观锁并不是绝对的对立关系,可以根据具体的应用场景选择不同的锁策略,甚至结合使用。例如,在某些情况下可以先使用乐观锁进行无锁操作,当发生冲突时转换为悲观锁来处理。