悲观锁
认为随时有可能发生冲突,用锁保护所有临界区。日常使用的锁绝大多数都是悲观锁。
优点:
1. 确保安全性,悲观锁临界区内不会发生并发问题。
2. 简单方便。
3. 使用悲观锁,在临界区内操作数据成功率高。
缺点:
1. 如果临界区内耗时长,会影响程序整体工作效率。
2. 可能产生死锁。
乐观锁
乐观的认为不会发生并发冲突,不为临界区代码加锁,但会持有在运行临界区前的版本号。在完成临界区后对比版本号,如果版本号没被更新说明没有产生冲突,进行数据更新并更新版本号。如果发现版本号被其他线程更新,则回滚所有操作并重试。乐观锁不是锁,是一种概念。
优点:
1. 不会产生死锁问题。
2. 在并行冲突情况低的多读程序中,工作效率比悲观锁高。
3. 允许多个线程进入临界区,在不同时对某些数据修改的情况下,效率很高。
缺点:
1. 对于回滚代价大的程序,不易使用乐观锁。
2. 冲突情况多时,效率不一定比悲观锁高。
3. 乐观锁实现复杂。
JAVA中乐观锁与悲观锁
java中较为常用的锁是synchronized和ReentrantLock,实际上这两种锁都是悲观锁,悲观锁就是一般的锁,java并不强调悲观和乐观的概念。在java并发里悲观和乐观的区分不如说是上锁和Ad-hoc线程封闭的区别。通俗的理解,悲观就是用锁控制多线程访问,乐观更多的是用程序本身的规则约束数据不能被错误的访问到。
如何在java中实现乐观锁?回到乐观锁的概念,用版本来控制数据不被错误的访问。在你的java程序中添加int活着double型的version字段,但是你要确保版本的访问和最后数据的修改锁是被锁保护的,这样做大概采用了乐观锁的概念,能够起到的效果是减少了临界区的范围,如果你的事务很长,采用这种方法尚可。不要在不能使用乐观锁的地方强行使用乐观锁,它的概念不是为java而生的而是数据库。不要弄巧成拙。
数据库中乐观锁与悲观锁
悲观锁实现
在对任意记录进行修改前,先尝试为该记录加上排他锁(exclusive locking)。
如果加锁失败,说明该记录正在被修改,那么当前查询可能要等待或者抛出异常。
如果成功加锁,那么就可以对记录做修改,事务完成后就会解锁了。
其间如果有其他对该记录做修改或加排他锁的操作,都会等待我们解锁或直接抛出异常。
悲观锁是一种默认产生冲突的处理概念,上述四个步骤表达了悲观锁的含义。
start transaction;
select A from B where C for update;
update B set D;
commit;
第一步开始事务,第二步select末尾for update
表示在此次事务提交完成之前select查询到的语句被锁定,在事务结束之前不会被改变。
乐观锁实现
在大项目中,数据库的每一张表至少有10多个字段。即使是很简单的一张表也会有很多字段,这是因为每一张表都有相同的一些记录性字段,比如:最后修改时间、最后修改人、版本号…我们可以利用其中的版本号来实现乐观锁。
select A,version from B where C
...(其他操作)
update B set D,version=version+1
where C and version = E;(E是已知最近版本号)
单单是数据库不便于操作,可以结合代码记录版本号,事务完成后进行验证。