在平时开发中,存在很多种锁,今天对几种锁都进行简单的说明一下。它们分别是悲观锁、乐观锁、可重入锁、读写锁、CAS(无所机制)、自旋锁
今天比较深入讨论的是前几种锁的定义,关于CAS和自旋锁,之后可能详细学习后再进行记录。
悲观锁和乐观锁
1、悲观锁,当使用悲观锁的时候,资源被一个操作者操作的时候,其它操作者都需要等待。具有独占性和排它性,在资源被处理的过程之中,一直处于锁定的状态。也可以说,其采用的是“先锁定在访问”的保守侧率。
举例,更新数据库,使用for update 就是典型的悲观锁,在更新之前,锁定数据,其它线程无法对资源进行操作。(使用其它文档的图)
所以,使用了悲观锁,每次只能有一个线程对资源进行操作,其它的线程全部阻塞,效率低下。(使用 select…for update 锁数据,需要注意锁的级别,MySQL InnoDB 默认行级锁。行级锁都是基于索引的,如果一条 SQL 语句用不到索引是不会使用行级锁的,会使用表级锁把整张表锁住,这点需要注意。)
2、乐观锁,乐观锁使用版本号对数据进行控制,当一个线程对数据进行修改的时候,伴随着版本号会进行修改,其它线程想修改数据的时候,就查询不到版本号了。适用于度多、写少的场景。(CAS就是乐观锁的体现)
从上述伪代码可知,当一个线程进行更新后,其它线程就找不到version=1的数据了,update语句也就失效了,这里是直接告诉程序失效,让其可以重新尝试,并不是进行阻塞。
可重入锁
可重入锁其实就是锁是集成的,也就是线程获得过某个锁,再次获取这个锁的时候,是不会出现死锁的情况的。
java中synchronized、ReentrantLock 都是可重入锁。废话不多上代码:
上面的代码可以展示,在m1方法获取到的是Test01的类锁,在没释放之前,m2方法也获取了Test01的类锁,但是没有出现死锁的问题,这就是可重入锁。
这篇文档,讲的很棒,例子很生动,大家可以看看,传送门。
读写锁
读写锁是Lock提供的一个针对读写操作的锁,当对一个资源进行写操作的时候,那么所有线程无法对其进行读操作,当没有线程进行写操作的时候,资源的读操作不受限制。
可以看到,当使用读写锁的时候,只有在写完操作之后,读操作才开。
Lock和synchronized的区别:
1、Lock支持非阻塞的方式获取锁,并且能够中断等待锁的线程(lockInterruptibly 可以调用Thread.interrupt 进行中断)
2、Lock必须手动释放锁,所以应该在finally中编写释放锁的代码。synchronized可以自动释放锁。
3、Lock支持公平锁、非公平锁。synchronized只支持非公平锁。