一、锁的基本简介

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。确保不会同时同一资源进行多重更新。

posted on 2016-07-13 17:28  何红霞  阅读(176)  评论(0编辑  收藏  举报