锁的概述
1.1 乐观锁与悲观锁
悲观锁指对数据被外界修改持保守态度,认为数据很容易就会被其他线程修改,所以在数据被处理前先对数据进行加锁,并在整个数据处理过程中,使数据处于锁定状态。悲观锁的实现往往依靠数据库提供的锁机制,即在数据库中,在对数据记录操作前给记录加排它锁。如果获取锁失败,则说明数据正在被其线程修改,当前线程则等待或者抛出异常。如果获取锁成功,则对记录进行操作,然后提交事务后释放排它锁。
下面我们看一个典型的例子,看它如何使用悲观锁来避免多线程同时对一个记录进行修改。
Public int update(long id){
//1.使用悲观锁获取指定记录
EntryObject entry=query(“select * from table1 where id=#{id} for update”,id);
//2.修改记录内容,根据计算修改entry记录的属性
String name=generatorName(entry);
Entry.setName(name);
//3.udpate操作
int count =update(“update table1 set name=#{name},age=#{age} where id=#{id}”,entry);
return count;
}
对于如上代码,假设updateEntry、query、update方法都使用了事务切面的方法,并且事务传播性被设置为required。执行updateEntry方法时,如果上层调用方法里面没有开启事务,则会即时开启一个事务,然后执行代码1。代码1调用了query方法,其根据指定id从数据库里面查询出一个记录。由于事务传播性为required,所以执行query时没有开启新的事务,而是加入了updateEntry开启的事务,也就是在updateEntry方法执行完毕提交事务时,query方法才会被提交,就是说记录的锁定会持续到updateEntry执行结束。2则对获取的记录进行修改,代码3把修改的内容写回数据库,同样代码3的update法也没有开启新的事务,而是加入了updateEntry的事务。也就是updateEntry、query、update方法共用同一事务。当多个线程同时调用updateEntry方法,并且传递的是同一个id时,只有一线程执行代码1会成功,其他线程则会被阻塞,这是因为在同时间只有一个线程可以获取对应记录的锁,在获取锁的线程释放锁前(updateEntry执行毕,提交事务前),其他线程必须等待,也就是在一时间只有一个线程可以对该记录进行修改。乐观锁是相对悲观锁来说的,它认为数在一般情况不会造成冲突,所以在访问记录前不会加排它锁,而是进行数据提交更新时,才会正式对数据冲突与否进行检测。具体来说,根据update返回的行数让用户决定如何去做。将上面的例子改为使用乐观锁的代码如下。
publicintupdateEntry(longid){
//(1)使用乐观锁获取指定记录
EntryObject entry=query(”select * from tablel whereid=#{id}”,id);
//(2)修改记录内容,version字段不能被修改
String name=generatorName(entry);
entry.setName(name);
//(3)update操作
int count=update(“update tablel set name=#{name},age=#{age},version=${version}+1 where
id=#{id} and version=#{version)”,entry);
return count;
}
在如上代码中,当多个线程调用updateEntry方法并且传递相同的id时,多个线程可以同时执行代码1获取id