悲观锁和乐观锁是两种处理并发访问的不同策略,它们关注的是在多个线程同时访问共享资源时如何保证数据一致性的问题。
1、悲观锁:
- 定义: 悲观锁的基本头脑是,在整个数据处理过程中,将共享资源进行加锁,以防止其他线程的干扰。
- 实现:
通常通过数据库的行锁或者Java中的synchronized关键字来实现。在悲观锁的情境下,线程以为在执行期间其他线程可能会修改共享资源,因此在访问共享资源之前,先获取锁,确保本身是独占资源的。比方 Collections.synchronizedMap 多线程Map用到的就是悲观锁。
2、乐观锁:
- 定义:
乐观锁的基本头脑是,在整个数据处理过程中,不对共享资源进行加锁,而是在访问时假设其他线程不会修改共享资源,只有在真正更新时才检查是否有冲突。
- 实现: 乐观锁的实现通常依靠于数据版本控制,通常通过版本号(版本控制)或者CAS(Compare and Swap)等机制来实现。在乐观锁的情境下,线程在读取共享资源时不会加锁,而是在更新时检查是否有其他线程修改过,如果有,则进行相应处理。
- 版本号:每条纪录可以包含一个版本号字段。读取纪录时记住版本号,在更新纪录时检查版本号是否发生变革(比如通过WHERE子句比力版本号)。如果版本号相同,则更新数据并增长版本号;如果不同,阐明数据在此期间已被其他变乱修改。
- 时间戳:使用时间戳也是一种常见的方式,操作雷同于版本号。
乐观锁适用于冲突较少的情况,因为它淘汰了锁的开销,但在高冲突情况中可能导致大量的重试和失败。
悲观锁: 就比如一个人在使用自动取款机(ATM)时,先取号列队,然后在本身的操作过程中,不让其他人插队或者干扰,确保本身独享ATM资源。
乐观锁:
就比如一个人在自动取款机前,直接去尝试取款,但在取款的时间,会检查本身的操作是否被其他人打搅,如果没有被打搅,就顺遂完成取款;如果有冲突,就须要重新尝试或者采取其他步调。
总的来说,悲观锁更加悲观地以为会有冲突,因此提前加锁以保护资源;而乐观锁更加乐观地以为冲突不会常常发生,因此先尝试操作,再在须要的时间进行冲突检测和处理。选择乐观锁还是悲观锁,取决于应用的具体需求、数据访问模式和性能考虑。乐观锁在读多写少的场景中表现较好,而悲观锁在写操作频繁的场景中更能保证数据的一致性。在现实应用中,这两种锁常常根据具体情况和业务需求机动使用。
3、实现
为了更好地明白乐观锁和悲观锁的具体实现,我们可以使用一个简单的银行账户余额更新作为例子。我们假设有一个名为 accounts 的表,此中包罗字段 account_id(账户标识)、balance(账户余额)和 version(版本号,用于乐观锁)。
3.1. 乐观锁实现
在使用乐观锁时,我们通常在表中添加一个 version 字段。每次读取纪录时,version 字段也会被读取,并在后续的更新中检查这个版本号是否改变。
- SQL 表布局
- CREATE TABLE accounts (
- account_id INT AUTO_INCREMENT PRIMARY KEY,
- balance DECIMAL(10, 2),
- version INT DEFAULT 1 );
复制代码 - 更新操作
在进行更新操作时,检查 version 字段确保数据未被修改过,然后进行更新,并将 version 字段的值增长1。
- UPDATE accounts
- SET balance = balance + 100, -- 假设我们要增加100元
- version = version + 1
- WHERE account_id = 1 AND version = @CurrentVersion;
复制代码 @CurrentVersion 是从应用程序传入的,基于最初查询得到的版本号
如果 version 不匹配,即另一个变乱已经更新了纪录,这个更新操作将不会改变任何行,应用程序可以据此知道更新失败,可能须要重新尝试或通知用户。
3.2. 悲观锁实现
在使用悲观锁时,可以使用数据库提供的锁机制(如行锁),确保在当前变乱完成之前,其他变乱不能修改被锁定的数据。
- SQL 表布局
- -- 使用与乐观锁相同的表结构
- CREATE TABLE accounts (
- account_id INT AUTO_INCREMENT PRIMARY KEY,
- balance DECIMAL(10, 2)
- );
复制代码 - 更新操作
在查询时使用 FOR UPDATE 语句来锁定命据行。这会在当前变乱完成之前制止其他变乱修改这些行。
- START TRANSACTION;
- SELECT balance
- FROM accounts
- WHERE account_id = 1
- FOR UPDATE; -- 锁定账户1
- -- 执行业务逻辑,比如增加余额
- UPDATE accounts
- SET balance = balance + 100
- WHERE account_id = 1;
- COMMIT;
复制代码 在这个例子中,SELECT ... FOR UPDATE 语句将制止其他任何尝试更新或读取(在须要相同锁的情况下)account_id 为 1 的行的变乱,直到当前变乱提交或回滚。
乐观锁和悲观锁各有用武之地,具体使用哪种锁机制取决于应用场景和并发级别。乐观锁适用于写操作较少的场景,悲观锁则适用于高冲突情况,尤其是在多用户频繁写入同一数据的场合。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |