一、锁的基本简介
1.1,为什么需要锁
首先,锁的概念产生,主要是为了解决并发性的问题。什么是并发性问题呢,比如:
Angel现在银行有个账号,里面有存款1000块。现在,Angel的账户,在两个地方分别执行操作。首先,Angel妈妈拿着主卡在四川给Angel账户存钱,读取出Angel的余额是1000块。其次,Angel拿着副卡在廊坊从账户里取钱,读取出angel 的余额是1000块。这时候,angel取了200块钱,余额变为1000-200=800块。可是angel妈妈执行存钱1000块操作,Angel余额变为1000+1000=2000(Angel妈妈最初读取出的余额为1000)这时候,Angel的余额就出现了问题,按照正常逻辑,账户余额为1000-200+1000=1800块。这个问题,就叫做更新丢失。Angel的账户,丢失了取款200的更新。为了解决这一个问题,我们需要引入锁的概念
1.2,悲观锁
悲观锁:通常是有数据库机制实现的,在整个过程中把数据锁住(查询),只要事务不释放(提交 / 回滚),那么任何用户都不能查看或修改。
正如上面的例子,Angel妈妈在四川,读取出了Angel的余额,那么angel的账户就被锁定了。angel就不能在廊坊对其账户进行操作,只有等到angel妈妈对这个账户的更新结束,也就是正常更新余额1000+1000=2000的业务结束,angel才能在廊坊执行取款操作。那么,悲观锁对外界的修改持保守态度,有效的保证了数据的一致性。但是,优点:它不适合多个用户并发访问。当一个锁住的资源不被释放掉的时候,这个资源永远不会被其他用户进行修改,容易造成无限期的等待,也就是等待超时。(想象一下,angel妈妈一直没有执行完存钱操作,angel取钱的道路该是多么的艰辛。。。或者说angel一直没有执行完取钱操作,angel妈妈的存钱道路该有多么心酸。。。)
那么,怎样能保证数据的一致性,又不会导致无期限的等待呢?
1.3,乐观锁
乐观锁,从本质上来说并不是一种锁,它大多数的使用是采用数据版本的方式(version)实现,一般在数据库中加入一个version字段,在读取数据的时候将version读取出来,在保存数据的时候判断version的值是否小于数据库中的version值,如果小于不予更新,否则给予更新。
如果运用乐观锁的实现机制去解决Angel的取款问题,则会发生什么呢?首先,angel妈妈在四川读出angel的余额1000,和其数据的版本号1;同时angel在廊坊也读取出了余额1000,和版本号1。这时候angel执行了取款操作200,更新余额为1000-200=800,同时将数据版本号更新为2。这时候angel妈妈执行存钱操作,而版本号1<2,所以不予以执行1000+1000=2000的更新操作。若是要存款,只能再次执行业务流程,这样,保证了数据的一致性。
1.4,Hibernate的加锁模式
A: LockMode.NONE : 无锁机制。
B:LockMode.WRITE : Hibernate 在 Insert 和 Update 记录的时候会自动获取。
C:LockMode.READ : Hibernate 在读取记录的时候会自动获取。
以上这三种锁机制一般由 Hibernate 内部使用,如 Hibernate 为了保证 Update过程中对象不会被外界修改,会在 save 方法实现中自动为目标对象加上 WRITE 锁。
D: LockMode.UPGRADE :利用数据库的 for update 子句加锁。
E: LockMode. UPGRADE_NOWAIT : Oracle 的特定实现,利用 Oracle 的 for update nowait 子句实现加锁。
二、实例分析Hibernate的锁机制
2.1,乐观锁
<span style="font-family:KaiTi_GB2312;font-size:18px;"><?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.angel.hibernate.Inventory" table="t_inventory" <span style="color:#ff0000;">optimistic-lock="version"</span>> <id name="itemNo"> <generator class="assigned"/> </id> <version name="version"/> <property name="itemName"/> <property name="quantity"/> </class> </hibernate-mapping></span>
测试类:
<span style="font-family:KaiTi_GB2312;font-size:18px;">package test.com.angel.hibernate; import junit.framework.TestCase; import org.hibernate.Session; import com.angel.hibernate.HibernateUtils; import com.angel.hibernate.Inventory; public class OptimisticLockingTest extends TestCase { public void testLoad1() { Session session = null; try { session = HibernateUtils.getSession(); session.beginTransaction(); Inventory inv = (Inventory) session.load(Inventory.class, "1002"); System.out.println("opt1-->itemNo=" + inv.getItemNo()); System.out.println("opt1-->itemName=" + inv.getItemName()); System.out.println("opt1-->version=" + inv.getVersion()); System.out.println("opt1-->quantity=" + inv.getQuantity()); inv.setQuantity(inv.getQuantity() - 200); session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } finally { HibernateUtils.closeSession(session); } } public void testLoad2() { Session session = null; try { session = HibernateUtils.getSession(); session.beginTransaction(); Inventory inv = (Inventory) session.load(Inventory.class, "1002"); System.out.println("opt2-->itemNo=" + inv.getItemNo()); System.out.println("opt2-->itemName=" + inv.getItemName()); System.out.println("opt2-->version=" + inv.getVersion()); System.out.println("opt2-->quantity=" + inv.getQuantity()); inv.setQuantity(inv.getQuantity() + 200); session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } finally { HibernateUtils.closeSession(session); } } } </span>当我们在方法1读取数据结束,但是未提交事务之前,紧接着执行方法2的时候,由于version字段的值被更改,所以会导致方法1执行不通过,从而保证了数据的一致性。
2.2,悲观锁
<span style="font-family:KaiTi_GB2312;font-size:18px;">package test.com.angel.hibernate; import junit.framework.TestCase; import org.hibernate.LockMode; import org.hibernate.Session; import com.angel.hibernate.HibernateUtils; import com.angel.hibernate.Inventory; public class OptimisticLockingTest extends TestCase { public void testLoad1() { Session session = null; try { session = HibernateUtils.getSession(); session.beginTransaction(); Inventory inv = (Inventory) session.load(Inventory.class, "1002",LockMode.UPGRADE); System.out.println("opt1-->itemNo=" + inv.getItemNo()); System.out.println("opt1-->itemName=" + inv.getItemName()); System.out.println("opt1-->version=" + inv.getVersion()); System.out.println("opt1-->quantity=" + inv.getQuantity()); inv.setQuantity(inv.getQuantity() - 200); session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } finally { HibernateUtils.closeSession(session); } } public void testLoad2() { Session session = null; try { session = HibernateUtils.getSession(); session.beginTransaction(); Inventory inv = (Inventory) session.load(Inventory.class, "1002",LockMode.UPGRADE); System.out.println("opt2-->itemNo=" + inv.getItemNo()); System.out.println("opt2-->itemName=" + inv.getItemName()); System.out.println("opt2-->version=" + inv.getVersion()); System.out.println("opt2-->quantity=" + inv.getQuantity()); inv.setQuantity(inv.getQuantity() + 200); session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } finally { HibernateUtils.closeSession(session); } } } </span>当我们在方法1读取数据结束,但是未提交事务之前,紧接着执行方法2的时候,由于添加悲观锁的缘故,方法2无法执行,只有当方法1进行了提交,方法2才能继续执行。
三、总结
加锁可以有效的解决并发问题,但是,这也得根据应用程序的具体情况而定。如果开发 的应用程序都是单人操作,那么根本就不必引入锁的概念。在这里,顺便总结一下数据中锁的基本分类:
共享 (S) 用于不更改或不更新数据的操作(只读操作),如 SELECT 语句。
更新 (U) 用于可更新的资源中。防止当多个会话在读取、锁定以及随后可能进行的资源更新时发生常见形式的死锁。
排它 (X) 用于数据修改操作,例如 INSERT、UPDATE 或 DELETE。确保不会同时同一资源进行多重更新。