悲观锁和乐观锁

悲观锁的实现
:悲观锁依赖于数据库机制,在整个过程中将数据锁定,其他任何人都不能读取或修改
乐观锁:
大多数基于数据版本记录机制(Version)的实现,一般是在数据库表中加入一个version字段,还有时间戳等机制
读取数据时将版本一同读出,之后更新数据时版本号加1,如果提交数据时版本号小于或者等于数据库中的版本号
则认为数据是过期的否则给予更新

下面我们举例说明:

package com.june.hibernate;

public class Inventory {
public Integer itemNo; //物料编号
public String itemName;//物料名称
public int quantity; //数量
........//get/set方法省略
}


当我们采用乐观锁时映射文件:

<class name="com.june.hibernate.Inventory" table="t_inventory">
<id name="itemNo">
<generator class="native" ></generator>
</id>
<property name="itemName" length="32" />
<property name="quantity"></property>
</class>

我们运行如下2个单元测试:
testLoad1();

public void testLoad1(){
Session session=null;
Transaction tx=null;
try{
session=HibernateUtil.getSession();
tx=session.beginTransaction();
Inventory inventory=(Inventory)session.load(Inventory.class,2,LockMode.UPGRADE);
System.out.println("@inventory.id="+inventory.getItemNo());
System.out.println("@inventory.itemName="+inventory.getItemName());
System.out.println("@inventory.quantity="+inventory.getQuantity());

System.out.println("@提货减去200...............");
inventory.setQuantity(inventory.getQuantity()-200);//(1)

tx.commit();
}catch(Exception e){
e.printStackTrace();
tx.rollback();
}finally{
HibernateUtil.colseSession(session);
}

}

testLoad2()

public void testLoad2(){
Session session=null;
Transaction tx=null;
try{
session=HibernateUtil.getSession();
tx=session.beginTransaction();
Inventory inventory=(Inventory)session.load(Inventory.class,2,LockMode.UPGRADE);
System.out.println("#inventory.id="+inventory.getItemNo());
System.out.println("#inventory.itemName="+inventory.getItemName());
System.out.println("#inventory.quantity="+inventory.getQuantity());

System.out.println("#提货减去200...............");
inventory.setQuantity(inventory.getQuantity()-200);

tx.commit();
}catch(Exception e){
e.printStackTrace();
tx.rollback();
}finally{
HibernateUtil.colseSession(session);
}

}
}

我们用debug方法testLoad1()的方法执行时 我们让方法执行到(1)(断点一定要设置它的前面)处停止下来,我们在来运行testLoad2() 我们会发现 在加载了对象之后,也就是
执行了查询sql语句之后会停下来;当我们运行完testLoad1()之后,也就是提交了事务,解除了悲观锁之后testLoad2()才能继续运行。

乐观锁:
我们来用数据版本来实现乐观锁
我们来看Inventory的生命

package com.june.hibernate;

public class Inventory {
public Integer itemNo;
public String itemName;
public int quantity;
public Integer version;
..............
}

映射文件

<class name="com.june.hibernate.Inventory"table="t_inventory" optimistic-lock="version">
<id name="itemNo">
<generator class="native" ></generator>
</id>
<!-- version一定要紧挨着Id-->
<version name="version"></version>
<property name="itemName" length="32" />
<property name="quantity"></property>
</class>

生成的数据库表中
[img]/upload/attachment/51000/03f3e4c5-7670-3906-8b3e-123137ea84f6.bmp[/img]

我们在来运行如下的单元测试:

/**
* testLoad2()
*/
public void testLoad1(){
Session session=null;
Transaction tx=null;
try{
session=HibernateUtil.getSession();
tx=session.beginTransaction();
Inventory inventory=(Inventory)session.load(Inventory.class,1);
System.out.println("@inventory.id="+inventory.getItemNo());
System.out.println("@inventory.itemName="+inventory.getItemName());
System.out.println("@inventory.quantity="+inventory.getQuantity());
System.out.println("@inventroy.version="+inventory.getVersion());
System.out.println("@提货减去200...............");
inventory.setQuantity(inventory.getQuantity()-200);

tx.commit();
}catch(Exception e){
e.printStackTrace();
tx.rollback();
}finally{
HibernateUtil.colseSession(session);
}

}
/**
* testLoad2()
*/
public void testLoad2(){
Session session=null;
Transaction tx=null;
try{
session=HibernateUtil.getSession();
tx=session.beginTransaction();
Inventory inventory=(Inventory)session.load(Inventory.class,1);
System.out.println("#inventory.id="+inventory.getItemNo());
System.out.println("#inventory.itemName="+inventory.getItemName());
System.out.println("#inventory.quantity="+inventory.getQuantity());

System.out.println("#提货减去200...............");
inventory.setQuantity(inventory.getQuantity()-200);

tx.commit();
}catch(Exception e){
e.printStackTrace();
tx.rollback();
}finally{
HibernateUtil.colseSession(session);
}

}

差点忘了,在悲观锁和乐观锁的测试运行之前一定要初始化数据
舒适化数据类如下

import org.hibernate.Session;
import org.hibernate.Transaction;

public class InitialData {
public static void main(String args[]){
Session session=null;
Transaction tx=null;
try{
session=HibernateUtil.getSession();
tx=session.beginTransaction();
Inventory inventory=new Inventory();
inventory.setItemName("Viager");
inventory.setQuantity(1000);
session.save(inventory);
tx.commit();
}catch(Exception e){
e.printStackTrace();
tx.rollback();
}finally{
HibernateUtil.colseSession(session);
}
}
}

通过以上的测试 我们可以对Hibernate悲观锁和乐观锁有个比较清晰的认识!!!
<think>嗯,用户现在想了解悲观锁乐观锁的概念。之前他们问过并行并发,看来他们对计算机科学中的并发控制机制感兴趣。现在需要详细解释这两个锁的概念,可能还要对比它们的区别应用场景。 首先,得明确悲观锁乐观锁的基本定义。悲观锁是假设最坏情况,每次操作数据时都加锁,防止其他线程访问。乐观锁则相反,假设冲突很少发生,只在提交时检查是否有冲突。需要举一些例子帮助理解,比如数据库中的行锁版本号机制。 接下来,用户可能需要知道它们的核心区别。比如,悲观锁适合写操作多的场景,而乐观锁适合读多写少的情况。还要提到它们的实现方式,比如数据库的SELECT FOR UPDATE是悲观锁,CAS操作或版本号是乐观锁。 另外,要考虑用户可能的实际应用场景。比如高并发下的库存扣减,悲观锁可能会导致性能问题,而乐观锁通过重试机制可能更高效。这时候需要举一些实际的例子,比如电商系统中的库存管理。 还要注意用户可能混淆两者的适用情况,需要明确指出优缺点。悲观锁的优点是简单直接,但容易引起阻塞;乐观锁减少锁竞争,但需要处理冲突重试,增加了复杂度。 最后,总结使用策略,帮助用户根据具体场景选择。比如在冲突频繁时用悲观锁,冲突较少时用乐观锁,或者结合两者使用。可能还需要提到一些技术实现,如Java中的synchronizedAtomic类,或者数据库的MVCC。 需要确保回答结构清晰,分点说明,对比明确,例子贴切。可能还需要检查是否有遗漏的关键点,比如CAS的具体过程,版本号如何工作,以及可能的问题如ABA问题及其解决方案。这样用户能全面理解这两个锁机制。</think>以下是关于**悲观锁****乐观锁**的对比解析,通过定义、实现逻辑适用场景帮助你理解两者的核心差异: --- ### **一、定义对比** 1. **悲观锁(Pessimistic Locking)** - **核心思想**:假设数据在操作时**一定会发生冲突**,因此**提前加锁**,阻止其他线程访问。 - **类比**:像“独占模式”——认为别人会修改数据,提前锁住资源。 - **典型场景**:银行转账、库存扣减(写操作频繁)。 2. **乐观锁(Optimistic Locking)** - **核心思想**:假设数据操作时**冲突概率低**,先不加锁,只在提交时检查数据是否被修改。 - **类比**:像“先操作再验证”——认为别人不会修改数据,通过版本号/时间戳判断冲突。 - **典型场景**:评论点赞、文档协作编辑(读多写少)。 --- ### **二、核心区别** | **特性** | **悲观锁** | **乐观锁** | |-----------------|-------------------------------------|-------------------------------------| | **冲突假设** | 默认高概率冲突,提前加锁 | 默认低概率冲突,提交时验证 | | **锁机制** | 显式加锁(如行锁、表锁) | 无显式锁,依赖版本号或CAS机制 | | **性能** | 高并发写场景下性能差(锁竞争) | 高并发读场景性能高(无锁竞争) | | **实现复杂度** | 简单(依赖数据库/语言内置锁) | 需手动处理冲突(重试或回滚) | | **数据一致性** | 强一致性 | 最终一致性 | --- ### **三、技术实现** 1. **悲观锁实现** - **数据库**:`SELECT ... FOR UPDATE`(行级锁) - **代码**:Java的`synchronized`关键字、`ReentrantLock` ```sql -- 示例:MySQL中锁定某行数据 BEGIN; SELECT stock FROM products WHERE id=1 FOR UPDATE; -- 加锁 UPDATE products SET stock=stock-1 WHERE id=1; COMMIT; ``` 2. **乐观锁实现** - **数据库**:版本号字段 + `WHERE`条件检查 - **代码**:CAS(Compare-And-Swap)、原子类(如`AtomicInteger`) ```sql -- 示例:通过版本号控制 UPDATE products SET stock=stock-1, version=version+1 WHERE id=1 AND version=current_version; -- 提交时验证版本 ``` ```java // Java中CAS实现(Atomic类) AtomicInteger atomicInt = new AtomicInteger(5); atomicInt.compareAndSet(5, 6); // 当前值为5时才更新为6 ``` --- ### **四、适用场景** 1. **优先选择悲观锁** - 写操作远多于读操作(如支付系统) - 数据强一致性要求高(如金融交易) 2. **优先选择乐观锁** - 读操作远多于写操作(如社交平台点赞) - 冲突概率低,且需要高吞吐量(如API限流计数器) 3. **混合使用** - 在分布式系统中,可结合两者: - 用乐观锁降低锁竞争 - 在关键操作(如余额修改)使用悲观锁 --- ### **五、典型问题与解决方案** 1. **悲观锁的缺点** - **死锁风险**:需设置锁超时时间(如MySQL的`innodb_lock_wait_timeout`) - **性能瓶颈**:通过缩小锁粒度(如行锁代替表锁)优化 2. **乐观锁的缺点** - **ABA问题**:版本号需递增,或用时间戳避免数值回滚 - **重试成本**:需设计补偿机制(如消息队列异步重试) --- ### **六、总结** - **悲观锁**:简单粗暴,适合“宁错杀不放过”的高冲突场景。 - **乐观锁**:轻量灵活,适合“相信世界平”的低冲突场景。 - **本质差异**:对并发冲突的**预判方式**不同,导致实现路径的分离。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值