数据库并发操作与synchronized的用法

最近在写调库审核的功能,生成调库文件时需要从数据库获取文件的批次号(VOUCH_NO)。因为在审核的时候有可能存在多个操作员同时进行审核的情况,获取批次号后对其修改,然后更新数据库里的批次号,这个操作需要互斥。我在自己电脑上模拟了一下这种情况,我在本机上的MySQL中建了一个SYS_PARAM表,并且拷贝了SysParamUtil类和公司封装的类库到项目中。以下事务控制内部的代码模拟的是ess项目中FileLogQueryBo.genFileLog()方法的实现(事务控制代码用于模拟ess中的AOP事务控制)。

package com.strikew.modules.fileLog;import com.strikew.bean.domain.SysParam;import com.strikew.constant.SysParamNameConst;import com.strikew.dao.SysParamDao; * 测试并发从数据库获取批次号是否会导致数据不一致 * @author Wangsiy * */class GetVouchNo implements Runnable {private static SysParamDao sysParamDao = new SysParamDao();public void run() {String rs = null;try {System.out.println(Thread.currentThread().getName() + ” transaction begin!”);HibernateUtil.beginTransaction();rs = TransactionNestUtil.reference();SysParam bean = SysParamUtil.getInstance().getSysParamByName(SysParamNameConst.VOUCH_NO);synchronized (bean) {int vouchNo = Integer.parseInt(bean.getCharVal());// 修改批次号vouchNo += 1;bean.setCharVal(String.valueOf(vouchNo));// update SysParamsysParamDao.makePersistent(bean, true);}TransactionNestUtil.releaseRef(rs);if (!TransactionNestUtil.isReference()) {HibernateUtil.commitTransaction();}System.out.println(Thread.currentThread().getName() + ” transaction commit!”);} catch (Exception e) {TransactionNestUtil.releaseRef(rs);if(!TransactionNestUtil.isReference()){HibernateUtil.rollbackTransaction();}System.err.println(e.getMessage());e.printStackTrace();} finally {if (!TransactionNestUtil.isReference()) {HibernateUtil.closeSession();}}}}public class FileLogQuery { main(String[] args) {Thread t1 = new Thread(new GetVouchNo());Thread t2 = new Thread(new GetVouchNo());t1.setName(“Thread 1”);t2.setName(“Thread 2”);t1.start();t2.start();}}

周五我看到这块代码时,认为有可能产生”Lost Update”(丢失更新)问题[见《Head First Java P512》]。当时我认为如果有A、B两个操作员(可以用两个线程代替),B进入了同步代码块(synchronized),更新了数据库里的批次号,此时在同步块外面的A获得的批次号仍然是旧的。今天我实验后发现我当时的认知是错误的。实验中我发现A、B线程中的bean引用的是堆里的同一个对象(因为SysParmUtil.getInstance()方法调用了内部的genSysParam()方法,获取数据表SYS_PARAM里的所有记录,将它们以<paramName, bean>的key-value形式存在了内部的HashTable里)因此B在同步块中对bean所做的修改在A中会得到反映,也就是说这里并不存在”Lost Update”的问题,因为Update的部分已经在同步块里了。不过前提是A、B两个线程在同一个Java虚拟机里,如果不在同一个Java虚拟机也就是说程序是分布式的,那A、B就不可能引用到同一个堆里的同一个对象。我推测将来电子凭证这个项目一定是部署在一台主机上的,操作员仅仅通过浏览器登录这个系统进行操作而已,所以并发审核的问题一定只是发生在同一个Java虚拟机环境里的。

再来看看《Head First Java》中描述的导致并发问题发生的原因:

      It all comes down to one potentially deadly scenario: two or      more threads have access to a single object’s data. In other      words, methods executing on two different stacks are both      calling, say, getters or setters on a single object on the heap.

多个线程同时操作堆里的同一个对象,由此就会引发数据不一致的问题。获取并更新批次号这个问题完全符合上述的定义:多个线程(A、B)操作(修改批次号)堆里的同一个对象(bean)。解决这种问题的思路就是要将多个操作捆绑在一起作为一个不可分割的原子操作。在上述代码中需要捆绑的就是以下的批次号修改和持久化操作:

         int vouchNo = Integer.parseInt(bean.getCharVal());// 修改批次号vouchNo += 1;bean.setCharVal(String.valueOf(vouchNo));// update SysParamsysParamDao.makePersistent(bean, true);

只有执行了makePersistent()(内部调用Hibernate的Session.save()方法)将bean持久化后,才真正的把对bean的修改转化成对数据库表中行的修改。

现在我们已经知道了要同步哪些操作了,接下来很重要的就是该考虑要对哪一个对象加锁。

synchronized的用法

没有了爱的语言,所有的文字都是乏味的

数据库并发操作与synchronized的用法

相关文章:

你感兴趣的文章:

标签云: