参考:http://blog.csdn.net/xiangwanpeng/article/details/55106732

  B/S构架的应用越来越普及,但由于它有别于C/S构架的特殊性,并发控制始终没能得到很好的解决,如售票系统经常会出现同一张火车票出售多次的现象。典型的案例如下:

例如若有两个客户端,A客户先读取了账户余额2000元,之后B客户也读取了账户余额2000元的数据,A客户提取了500元,对数据库作了变更,此时数据库中的余额为1500元,B客户也要提取1300元,根据其所取得的资料,2000-1300将为700余额,若此时再对数据库进行变更,最后的余额700元就会不正确,应当是200元,问题的出现是由于两个客户对同一条数据进行并发访问造成的。

Web应用中并发控制的特殊性

上述问题在C/S构架中可以通过长事务来实现,但Web应用是基于Internet网络环境的,其中的并发控制有其内在的特殊性:

1. Web所基于的网络协议HTTP(Hyper Text Transfer Protocol)是一种无连接的协议,数据库服务器无法保存事务的状态信息;

2. 用户可以随时中止或启动浏览器中当前主页上的事务。

由于上述特殊性,Web应用中并发控制不能采用严格的长事务来实现,但可以长事务的思路来实现,在数据读取的时候把相应的数据锁定,在更新阶段把锁放开,然后更新数据。

  web应用中并发控制的实现:这里主要介绍乐观锁和悲观锁

  业务逻辑的实现过程中,往往需要保证数据访问的排他性。如在 金融 系统的日终结算处理中,我们希望针对某个cut-off时间点的数据进行处理,而不希望在结算进行过程中(可能是几秒种,也可能是几个小时),数据再发生变化。此时,我们就需要通过一些机制来保证这些数据在某个操作过程中不会被外界修改,这样的机制,就是所谓的“锁”,即给选定的目标数据上锁,使其无法被其他程序修改。有两种锁机制:即通常所说的“乐观锁(Optimistic Locking)” 和“悲观锁(Pessimistic Locking)”。

1.乐观锁(Optimistic Locking)

乐观锁(optimistic locking)则乐观的认为资料的存取很少发生同时存取的问题,因而不作数据库层次上的锁定,为了维护正确的数据,乐观锁定使用应用程序上的逻辑实现版本控制来解决。

并发控制时,数据不一致的情况一旦发生,有几个解决的 方法 ,一种是先更新为主,一种是后更新的为主,比较复杂的就是检查发生变动的数据来实现,或是检查所有属性来实现乐观锁定。

hibernate通过版本号检查来实现后更新为主,这也是Hibernate所推荐的方式,在数据库中加入一个VERSION栏记录,在读取数据时连同版本号一同读取,并在更新数据时递增版本号,然后比对版本号与数据库中的版本号,如果大于数据库中的版本号则予以更新,否则就回报错误。

以Hibernate实现版本号控制锁定的话,我们的对象中增加一个version属性,例如:

public class MyAccount {
private int version;
....
public void setVersion(int version) {
this.version = version;
}
public int getVersion() {
return version;
}
....
}

而在映像文件中,我们使用optimistic-lock属性设定version控制,属性栏之后增加一个标签,例如:

optimistic-lock="version"

设定好版本控制之后,在上例中如果B客户试图更新数据,将会引发StableObjectStateException例外,我们可以捕捉这个例外,在处理中重新读取数据库中的数据,同时将B客户目前的数据与数据库中的数据读出来,让B客户有机会比对不一致的数据,以决定要变更的部份,或者您可以设计程式自动读取新的资料,并重复扣款业务流程,直到数据可以更新为止,这一切可以在后台执行,而不用让您的客户知道。在其它架构中也可通过这种思路来实现乐观锁,但版本控制和冲突的检测要在自己程序的程序中实现和维护。

2.悲观锁(Pessimistic Locking)

虽然乐观锁能够提高系统的性能,但它是对发生冲突的访问进行事后的补救,应用在用户输入数据量很少的场合比较适合,但如果在 企业 ERP,用户与系统交互涉及大量数据在页面表单上录入,如果事后提交失败后才提示用户要重新录入是很不现实的,所以有必要进行事前控制,这就要采用悲观锁。

在多个客户端可能读取同一笔数据或同时更新一笔数据的情况下,防止同一个数据被修改而造成混乱,最简单的手段就是在读取时对数据进行锁定,其它客户端不能对同一笔数据进行更新的读取动作。

悲观锁定(Pessimistic Locking)一如其名称所示,悲观的认定每次资料存取时,其它的客户端也会存取同一笔数据,因此对该笔数据进行事先锁定,直到自己操作完成后解除锁定。

悲观锁定通常透过系统或数据库本身的功能来实现,依赖系统或数据库本身提供的锁定机制,Hibernate即是如此,我们可以利用Query或Criteria的setLockMode()方法来设定要锁定的表或列(row)及其锁定模式,锁定模式有以下的几个:

LockMode.WRITE:在insert或update时进行锁定,Hibernate会在save()方法时自动获得锁定。

LockMode.UPGRADE:利用SELECT … FOR UPDATE进行锁定。

LockMode.UPGRADE_NOWAIT:利用SELECT … FOR UPDATE NOWAIT进行锁定,在Oracle环境下使用。

LockMode.READ:在读取记录时Hibernate会自动获得锁定。

LockMode.NONE:没有锁定。

也可以在使用Session的load()或是lock()时指定锁定模式以进行锁定。

如果数据库不支持所指定的锁定模式,Hibernate会选择一个合适的锁定替换,而不是丢出一个例外。

3.其它构架中悲观锁的实现

Hibernate的悲观锁,也是基于数据库的锁机制实现。下面的代码实现了对“用户”查询记录的加锁:

String sqlStr = "from userInfo as user where user.userId=’admin’";
Query query = session.createQuery(sqlStr);
query.setLockMode("user",LockMode.UPGRADE); //加锁
List userList = query.list();//执行查询,获取数据

query.setLockMode对查询语句中,特定别名所对应的记录进行加锁(我们为userInfo类指定了一个别名“user”),这里也就是对返回的所有user记录进行加锁:

select tuser0_.id as id, tuser0_.userId as userId, tuser0_.group_id as group_id, 
tuser0_.user_type as user_type, tuser0_.sex as sex from t_user tuser0_ where
(tuser0_.userId =’admin’ ) for update

通过上述转换后的sql语句可知,Hibernate的加锁其实是利用了数据库的for update语句,在读取阶段对某条记录的锁定,而在更新阶段提交,释放锁。

其实其它架构也可以采取该思路,不过,数据库的for update语句的锁定和释放一定要在数据的同一个连接中,如果读取阶段和更新阶段不是统一连接,即读取之后断开了与数据库的连接,则for update语句的锁定立即失效,为此,如果其它架构中要采取这种方式则要做相应的调整。

首先,由于Web应用是无状态的,也就是说数据库的for update语句的锁定和释放不一定是数据的同一个连接,为此,采用痕迹跟踪法,在读取数据时生成唯一的序列号(serialId),建立与数据连接的映射,并放置一个map数据结构中;在更新时,通过该serialId在连接池中重新获取该连接,用该连接去更新数据。

如果系统是采用dao读取数据,实体bean去更新数据,则只要在更新数据之前断开读取数据时的连接,则可以通过其它途径更新数据,如下代码所示:

public void update (AbstractEntityData data, String[] selTeamName ,String serialId) 
throws Exception {
dao.closeConnect(serialId);
bo.update(data);
}

其中,dao.closeConnect(serialId)是断开数据连接,bo.update(data)是通过EJB更新数据库

4.序列号(serialId)的创建和维护

由于不同用户可能同时建立连接或同一用户先后建立连接,故创建序列号可以在读取数据时通过sessionId和时间戳组合而成。而在操作的过程中,为了保持序列号不会丢失和唯一性,它不能放在session或application中,而是放在页面的request对象里,通过它向其它页面传递。

5.关联表的锁定

其实,Hibernate的悲观锁方式只能对单个表的记录进行锁定,但现实中,存在关联更新的情况,即在更新主表的时候有可能会更新到与之相关的子表,与此同时,其它用户也可能通过其它主表更新相应的子表同一条记录。

有两种方式处理,一是在读取数据通过sql语句关联子表相应记录,因为for update对所有关联表中符合条件的记录都会加锁;二是为子表找一个入口表,在更新子表的同时,必须更新子表的入口表。

6.例外操作的处理

采用这种方式,有一些例外情况必须小心处理,一是页面的关闭,如果调用相应的方法,如onbeforeunload()等,释放对应的数据库连接;二是用户非正常关机退出系统,必须有数据库周期清除无用的连接,如间隔二十分钟等,来释放读取时对数据的锁定,否则,该数据会长时间被锁定,直至应用服务器重启。

结论

软件系统的并发控制一般是通过加锁来实现,同样,Web应用也是采用乐观锁和悲观锁来实现,乐观锁是一种事后补救措施,是通过程序的逻辑控制版本来实现的,而悲观锁是事前的一种预防措施,它利用数据库的锁机制来实现,Hibernate对它做了一层封装,使应用更加方便,为了让其它架构都能适用,本文还原了Hibernate的实现原理,提出一般的实现思路和注意实现。

乐观锁,悲观锁,行锁,表锁,共享锁,排它锁之间的关系。

悲观锁包括:行锁表锁、共享锁、排他锁

而乐观锁的一种实现方式是通过CAS来实现乐观锁的。

 Synchronized 和lock之间的区别与优缺点:

web应用中并发控制的实现,各种锁的集合的更多相关文章

  1. web开发中的两把锁之数据库锁:(高并发--乐观锁、悲观锁)

    这篇文章讲了 1.同步异步概念(消去很多疑惑),同步就是一件事一件事的做:sychronized就是保证线程一个一个的执行. 2.我们需要明白,锁机制有两个层面,一种是代码层次上的,如Java中的同步 ...

  2. 【mysql】mysql增加version字段实现乐观锁,实现高并发下的订单库存的并发控制,通过开启多线程同时处理模拟多个请求同时到达的情况 + 同一事务中使用多个乐观锁的情况处理

    mysql增加version字段实现乐观锁,实现高并发下的订单库存的并发控制,通过开启多线程同时处理模拟多个请求同时到达的情况 ==================================== ...

  3. 第十一章:WEB浏览器中的javascript

    客户端javascript涵盖在本系列的第二部分第10章,主要讲解javascript是如何在web浏览器中实现的,这些章节介绍了大量的脚本宿主对象,这些对象可以表示浏览器窗口.文档树的内容.这些章节 ...

  4. Web应用中的轻量级消息队列

    Web应用中为什么会需要消息队列?主要原因是由于在高并发环境下,由于来不及同步处理,请求往往会发生堵塞,比如说,大量的insert,update之类的请求同时到达mysql,直接导致无数的行锁表锁,甚 ...

  5. PHP程序中的文件锁、互斥锁、读写锁使用技巧解析

    文件锁全名叫 advisory file lock, 书中有提及. 这类锁比较常见,例如 mysql, php-fpm 启动之后都会有一个pid文件记录了进程id,这个文件就是文件锁. 这个锁可以防止 ...

  6. 如何用 Java 实现 Web 应用中的定时任务?

    定时任务,是指定一个未来的时间范围执行一定任务的功能.在当前WEB应用中,多数应用都具备任务调度功能,针对不同的语音,不同的操作系统, 都有其自己的语法及解决方案,windows操作系统把它叫做任务计 ...

  7. JavaScript权威指南--WEB浏览器中的javascript

    知识要点 1.客户端javascript window对象是所有客户端javascript特性和API的主要接入点.它表示web浏览器的一个窗口或窗体,并且可以用window表示来引用它.window ...

  8. 如何用 Java 实现 Web 应用中的定时任务

    定时任务,是指定一个未来的时间范围执行一定任务的功能.在当前WEB应用中,多数应用都具备任务调度功能,针对不同的语音,不同的操作系统, 都有其自己的语法及解决方案,windows操作系统把它叫做任务计 ...

  9. java: web应用中不经意的内存泄露

    前面有一篇讲解如何在spring mvc web应用中一启动就执行某些逻辑,今天无意发现如果使用不当,很容易引起内存泄露,测试代码如下: 1.定义一个类App package com.cnblogs. ...

随机推荐

  1. 统计学习方法——KD树最近邻搜索

    李航老师书上的的算法说明没怎么看懂,看了网上的博客,悟出一套循环(建立好KD树以后的最近邻搜索),我想应该是这样的(例子是李航<统计学习算法>第三章56页:例3.3): 步骤 结点查询标记 ...

  2. Ecshop系统二次开发教程及流程演示

      来源:互联网 作者:佚名 时间:03-01 16:05:31 [大 中 小] Ecshop想必大家不会觉得陌生吧,大部分的B2C独立网店系统都用的是Ecshop系统,很受用户的喜爱,但是由于Ecs ...

  3. VS中MFC项目文件特别大的解决办法

    转 来自http://m.zhizuobiao.com/vc/vc-18082800177/ 自己插个眼 项目文件比较大因为 项目下有个隐藏文件夹.vs  下面是解决办法 本文主要向大家介绍了VC编程 ...

  4. HTML设置目标页面在新窗口打开

    在使用<a></a>标签进行超链接跳转时,目标页面默认在当前页面中打开. 如果希望当前页面中所有超链接跳转页面的时候,都在新窗口中打开,那么只需要在head标签中设置 base ...

  5. Vue 中使用Ajax请求

    Vue 项目中常用的 2 个 ajax 库 (一)vue-resource vue 插件, 非官方库,vue1.x 使用广泛 vue-resource 的使用 在线文档   https://githu ...

  6. Java提升四:Stream流

    1.Stream流的定义 Stream是Java中的一个接口.它的作用类似于迭代器,但其功能比迭代器强大,主要用于对数组和集合的操作. Stream中的流式思想:每一步只操作,不存储. 2.Strea ...

  7. 页面的html调试

    点击页面按下键盘的F12,或者鼠标右键选择检查(N) 会弹出一个窗口,这个窗口就是调试窗口 如上图所示,第一个图标是标签元素选择器,点击使用后,在页面上移动,会在Elements的区域找到你鼠标选中的 ...

  8. Django(三) 模型:ORM框架、定义模型类并创建一个对应的数据库、配置Mysql数据库

    一.模型概述 https://docs.djangoproject.com/zh-hans/3.0/intro/tutorial02/ https://www.runoob.com/django/dj ...

  9. JuJu alpha

    JuJu alpha阶段总结博客 JuJu   设想与目标   我们的软件要解决什么问题?是否定义得很清楚?是否对典型用户和典型场景有清晰的描述? 在cao ying researcher给的资料中定 ...

  10. 微信公众号开发 获取openid时报错40029 invalid code 问题的解决

    {"errcode":40029,"errmsg":"invalid code, hints: [ req_id: aELCyY4ce-WOFLAa ...