锁(locking)

业务逻辑的实现过程中,往往需要保证数据访问的排他性。如在金融系统的日终结算

处理中,我们希望针对某个cut-off时间点的数据进行处理,而不希望在结算进行过程中

(可能是几秒种,也可能是几个小时),数据再发生变化。此时,我们就需要通过一些机

制来保证这些数据在某个操作过程中不会被外界修改,这样的机制,在这里,也就是所谓

的“锁”,即给我们选定的目标数据上锁,使其无法被其他程序修改。

Hibernate支持两种锁机制:即通常所说的“悲观锁(Pessimistic Locking)”

和“乐观锁(Optimistic Locking)”。

悲观锁(Pessimistic Locking)

悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自

外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定

状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能

真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系

统不会修改数据)。

一个典型的倚赖数据库的悲观锁调用:

select * from account where name=”Erica” for update

这条sql 语句锁定了account 表中所有符合检索条件(name=”Erica”)的记录。

本次事务提交之前(事务提交时会释放事务过程中的锁),外界无法修改这些记录。

Hibernate的悲观锁,也是基于数据库的锁机制实现。

下面的代码实现了对查询记录的加锁:

String hqlStr =

"from TUser as user where user.name='Erica'";

Query query = session.createQuery(hqlStr);

query.setLockMode("user",LockMode.UPGRADE); //加锁

List userList = query.list();//执行查询,获取数据

query.setLockMode对查询语句中,特定别名所对应的记录进行加锁(我们为

TUser类指定了一个别名“user”),这里也就是对返回的所有user记录进行加锁。

观察运行期Hibernate生成的SQL语句:

select tuser0_.id as id, tuser0_.name as name, tuser0_.group_id

as group_id, tuser0_.user_type as user_type, tuser0_.sex as sex

from t_user tuser0_ where (tuser0_.name='Erica' ) for update

这里Hibernate通过使用数据库的for update子句实现了悲观锁机制。

Hibernate的加锁模式有:

Ø LockMode.NONE : 无锁机制。

Ø LockMode.WRITE :Hibernate在Insert和Update记录的时候会自动

获取。

Ø LockMode.READ : Hibernate在读取记录的时候会自动获取。

以上这三种锁机制一般由Hibernate内部使用,如Hibernate为了保证Update

过程中对象不会被外界修改,会在save方法实现中自动为目标对象加上WRITE锁。

Ø LockMode.UPGRADE :利用数据库的for update子句加锁。

Ø LockMode. UPGRADE_NOWAIT :Oracle的特定实现,利用Oracle的for

update nowait子句实现加锁。

上面这两种锁机制是我们在应用层较为常用的,加锁一般通过以下方法实现:

Criteria.setLockMode

Query.setLockMode

Session.lock

注意,只有在查询开始之前(也就是Hiberate 生成SQL 之前)设定加锁,才会

真正通过数据库的锁机制进行加锁处理,否则,数据已经通过不包含for update

子句的Select SQL加载进来,所谓数据库加锁也就无从谈起。

乐观锁(Optimistic Locking)

相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依

靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库

性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。

如一个金融系统,当某个操作员读取用户的数据,并在读出的用户数据的基础上进

行修改时(如更改用户帐户余额),如果采用悲观锁机制,也就意味着整个操作过

程中(从操作员读出数据、开始修改直至提交修改结果的全过程,甚至还包括操作

员中途去煮咖啡的时间),数据库记录始终处于加锁状态,可以想见,如果面对几

百上千个并发,这样的情况将导致怎样的后果。

乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本

(Version)记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于

数据库表的版本解决方案中,一般是通过为数据库表增加一个“version”字段来

实现。

读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提

交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据

版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。

对于上面修改用户帐户信息的例子而言,假设数据库中帐户信息表中有一个

version字段,当前值为1;而当前帐户余额字段(balance)为$100。

1 操作员A 此时将其读出(version=1),并从其帐户余额中扣除$50

($100-$50)。

2 在操作员A操作的过程中,操作员B也读入此用户信息(version=1),并

从其帐户余额中扣除$20($100-$20)。

3 操作员A完成了修改工作,将数据版本号加一(version=2),连同帐户扣

除后余额(balance=$50),提交至数据库更新,此时由于提交数据版本大

于数据库记录当前版本,数据被更新,数据库记录version更新为2。

4 操作员B完成了操作,也将版本号加一(version=2)试图向数据库提交数

据(balance=$80),但此时比对数据库记录版本时发现,操作员B提交的

数据版本号为2,数据库记录当前版本也为2,不满足“提交版本必须大于记

录当前版本才能执行更新“的乐观锁策略,因此,操作员B 的提交被驳回。

这样,就避免了操作员B 用基于version=1 的旧数据修改的结果覆盖操作

员A的操作结果的可能。

从上面的例子可以看出,乐观锁机制避免了长事务中的数据库加锁开销(操作员A

和操作员B操作过程中,都没有对数据库数据加锁),大大提升了大并发量下的系

统整体性能表现。

需要注意的是,乐观锁机制往往基于系统中的数据存储逻辑,因此也具备一定的局

限性,如在上例中,由于乐观锁机制是在我们的系统中实现,来自外部系统的用户

余额更新操作不受我们系统的控制,因此可能会造成脏数据被更新到数据库中。在

系统设计阶段,我们应该充分考虑到这些情况出现的可能性,并进行相应调整(如

将乐观锁策略在数据库存储过程中实现,对外只开放基于此存储过程的数据更新途

径,而不是将数据库表直接对外公开)。

Hibernate 在其数据访问引擎中内置了乐观锁实现。如果不用考虑外部系统对数

据库的更新操作,利用Hibernate提供的透明化乐观锁实现,将大大提升我们的

生产力。

Hibernate中可以通过class描述符的optimistic-lock属性结合version

描述符指定。

现在,我们为之前示例中的TUser加上乐观锁机制。

1. 首先为TUser的class描述符添加optimistic-lock属性:

<hibernate-mapping>

<class

name="org.hibernate.sample.TUser"

table="t_user"

dynamic-update="true"

dynamic-insert="true"

optimistic-lock="version"

>

……

</class>

</hibernate-mapping>

optimistic-lock属性有如下可选取值:

Ø none

无乐观锁

Ø version

通过版本机制实现乐观锁

Ø dirty

通过检查发生变动过的属性实现乐观锁

Ø all

通过检查所有属性实现乐观锁

其中通过version实现的乐观锁机制是Hibernate官方推荐的乐观锁实现,同时也

是Hibernate中,目前唯一在数据对象脱离Session发生修改的情况下依然有效的锁机

制。因此,一般情况下,我们都选择version方式作为Hibernate乐观锁实现机制。

2. 添加一个Version属性描述符

<hibernate-mapping>

<class

name="org.hibernate.sample.TUser"

table="t_user"

dynamic-update="true"

dynamic-insert="true"

optimistic-lock="version"

>

<id

name="id"

column="id"

type="java.lang.Integer"

>

<generator class="native">

</generator>

</id>

<version

column="version"

name="version"

type="java.lang.Integer"

/>

……

</class>

</hibernate-mapping>

注意version 节点必须出现在ID 节点之后。

这里我们声明了一个version属性,用于存放用户的版本信息,保存在TUser表的

version字段中。

此时如果我们尝试编写一段代码,更新TUser表中记录数据,如:

Criteria criteria = session.createCriteria(TUser.class);

criteria.add(Expression.eq("name","Erica"));

List userList = criteria.list();

TUser user =(TUser)userList.get(0);

Transaction tx = session.beginTransaction();

user.setUserType(1); //更新UserType字段

tx.commit();

每次对TUser进行更新的时候,我们可以发现,数据库中的version都在递增。

而如果我们尝试在tx.commit 之前,启动另外一个Session,对名为Erica 的用

户进行操作,以模拟并发更新时的情形:

Session session= getSession();

Criteria criteria = session.createCriteria(TUser.class);

criteria.add(Expression.eq("name","Erica"));

Session session2 = getSession();

Criteria criteria2 = session2.createCriteria(TUser.class);

criteria2.add(Expression.eq("name","Erica"));

List userList = criteria.list();

List userList2 = criteria2.list();TUser user =(TUser)userList.get(0);

TUser user2 =(TUser)userList2.get(0);

Transaction tx = session.beginTransaction();

Transaction tx2 = session2.beginTransaction();

user2.setUserType(99);

tx2.commit();

user.setUserType(1);

tx.commit();

执行以上代码,代码将在tx.commit()处抛出StaleObjectStateException异

常,并指出版本检查失败,当前事务正在试图提交一个过期数据。通过捕捉这个异常,我

们就可以在乐观锁校验失败时进行相应处理。

转载于:https://www.cnblogs.com/zengjin93/p/5340988.html

Java 中的悲观锁和乐观锁的实现相关推荐

  1. 聊一聊Java中的悲观锁和乐观锁

    前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家.点击跳转到网站. 文章目录 悲观锁(Pessimistic Locking) 悲观锁存的问题: 乐观锁 乐观锁存在的问 ...

  2. Java中的锁机制 -- 乐观锁、悲观锁、自旋锁、可重入锁、读写锁、公平锁、非公平锁、共享锁、独占锁、重量级锁、轻量级锁、偏向锁、分段锁、互斥锁、同步锁、死锁、锁粗化、锁消除

    文章目录 1. Java中的锁机制 1.1 乐观锁 1.2 悲观锁 1.3 自旋锁 1.4 可重入锁(递归锁) 1.5 读写锁 1.6 公平锁 1.7 非公平锁 1.8 共享锁 1.9 独占锁 1.1 ...

  3. 探索JAVA并发 - 悲观锁和乐观锁

    作者:acupt,专注Java,架构师社区合伙人! 什么是悲观锁,什么是乐观锁,它们是如何实现的? 定义 悲观锁:对世界充满不信任,认为一定会发生冲突,因此在使用资源前先将其锁住,具有强烈的独占和排他 ...

  4. Java 中15种锁的介绍:公平锁,可重入锁,独享锁,互斥锁,乐观锁,分段锁,自旋锁等等...

    http://blog.51cto.com/13919357/2339446 Java 中15种锁的介绍 在读很多并发文章中,会提及各种各样锁如公平锁,乐观锁等等,这篇文章介绍各种锁的分类.介绍的内容 ...

  5. 详解各种锁:CAS、共享锁、排它锁、互斥锁、悲观锁、乐观锁、行级锁、表级锁、页级锁、死锁、JAVA对CAS的支持、ABA问题、AQS原理

    共享锁(S锁) 又称为读锁,可以查看但无法修改和删除的一种数据锁.如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排它锁.获准共享锁的事务只能读数据,不能修改数据. 共享锁下其它用 ...

  6. Java死锁、活锁,悲观锁、乐观锁

    1.死锁与活锁的区别,死锁与饥饿的区别? 死锁:是指两个或两个以上的进程(或线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去. 产生死锁的必要条件: 互斥条 ...

  7. Java 中15种锁的介绍:公平锁,可重入锁,独享锁,互斥锁,乐观锁,分段锁,自旋锁等等

    Java 中15种锁的介绍 在读很多并发文章中,会提及各种各样锁如公平锁,乐观锁等等,这篇文章介绍各种锁的分类.介绍的内容如下: 公平锁 / 非公平锁 可重入锁 / 不可重入锁 独享锁 / 共享锁 互 ...

  8. **Java有哪些悲观锁的实现_「Java并发编程」何谓悲观锁与乐观锁,Java编程你会吗...

    何谓悲观锁与乐观锁 悲观锁 乐观锁 两种锁的使用场景 乐观锁常见的两种实现方式 1. 版本号机制 2. CAS算法 乐观锁的缺点 1 ABA 问题 2 循环时间长开销大 3 只能保证一个共享变量的原子 ...

  9. Java多线程学习十二:悲观锁和乐观锁的本质||

    悲观锁和乐观锁是从是否锁住资源的角度进行分类的. 悲观锁 悲观锁比较悲观,它认为如果不锁住这个资源,别的线程就会来争抢,就会造成数据结果错误,所以悲观锁为了确保结果的正确性,会在每次获取并修改数据时, ...

  10. JAVA并发编程:悲观锁与乐观锁

    生活 晴. 悲观与乐观的情绪概念 本篇来了解一下悲观锁和乐观锁,在了解这两个锁之前,我们首先有必要把悲观和乐观这两个词搞清楚: 悲观:对世事怀有消极的看法,认为事物总往糟糕的方向发展. 乐观:对世事怀 ...

最新文章

  1. Android开发——布局性能优化的一些技巧(一)
  2. C++ 枚举类型基本知识
  3. 局部内部类访问方法中的局部变量为什么加final
  4. hdu 1560 DNA sequence(迭代加深搜索)
  5. Vuex——使用namespace的store使用mapState获取state为undefined
  6. 表驱动法——直接访问表示例1
  7. sublime 安装 Package Control(笔记)
  8. mysql8.0连接错误_MySql 8.0连接失败
  9. char 转换 二进制 java_使用Java读取二进制文件并将其转换为char文件 - java
  10. UOS桌面操作系统专业版字体
  11. 微信安装正确操作方法
  12. 免费下载IEEE、SCI论文的网站
  13. SM2证书的鉴定方法
  14. 知识蒸馏 综述 Knowledge Distillation: A Survey
  15. 【Windows编程】Windows Socket API介绍
  16. 【保姆级教程】三角网生成库---triangle快速入门及使用说明(再不会就说不过去了啊兄弟)
  17. gephi绘制红楼梦关系图
  18. 结构化思维(Structured Thinking)
  19. KMS命令激活office2016
  20. Spring常用设计模式--简单工厂模式

热门文章

  1. 对待棘手bug,新手与大牛的差距在哪里?
  2. 新版“峡谷第一美”妲己尾巴毛发制作分享
  3. 朱峰谈概念设计(八):电影中的概念设计
  4. 《最后的守护者》的开发技术猜想
  5. 【Auto.js】QQ自动回赞_简易版
  6. ORACLE 10G以后的ORDER BY操作优化
  7. 使用 Authid Current_User 为调用者授权
  8. 设置Linux虚拟机和主机在同一网段
  9. 恢复Win10照片查看器
  10. Magento学习手记(第十天)