锁机制
锁机制的作用是保证并发安全问题,当且仅当存在有状态的共享变量时,才存在并发安全问题。
- 有状态就是,有数据存储功能。 有状态对象,就是有实例变量的对象,可以保存数据,是非线程安全的。 在不同方法调用间不保留任何状态。
- 无状态就是,一次操作,不能保存或者修改数据。 无状态对象,就是没有实例变量的对象,不能保存或修改数据,是不变类,是线程安全的。
相关阅读:
- 并发安全中的有状态和无状态是什么意思?
MySQL锁机制
锁的本质就是用来解决并发问题的,而MySQL锁实际上锁的是索引。
相关阅读:
MySQL的锁机制分为读锁和写锁。这里我们只聊写锁,对于MySQL写锁的实现,其实可以使用乐观锁和悲观锁机制实现。乐观锁利用版本号完成,是我们用户完成的;MySQL对于写锁的实现是悲观锁。
相关阅读:
从粒度上看,有表级锁和行级锁,表级锁分为意向锁和普通表锁;行级锁主要有记录锁、间隙锁(间隙锁在可重复读隔离级别下才有效)和临键锁。其中,行锁是加在索引上的一种锁,是二阶段锁,在事务结束后释放。
行锁实际加在索引上,所以如果一个update语句的where条件中,如果没有明确的索引时,会导致所有行均被加上行锁(所有间隙也会加上间隙锁)
所以,如果在使用select for update时忘了加索引,就会导致行锁升级为表锁,进而引发死锁。
意向锁从本质上来看,其实称不上是一把锁,主要是为了解决遍历记录导致的性能损耗。
相关阅读:
行级锁的三把锁,起到的作用就是通过协作,避免修改范围内的数据发生额外的变化,这就是为什么,MySQL加锁的基本单位其实是临键锁next key,因为临键锁=记录锁+间隙锁。
好的,那么知道了这个概念之后,我们就需要知道三把锁是什么,以及怎么实现行级锁?
- 由于行级锁的本质是防止目标范围内的数据发生变化,那么我们应该需要防止哪些操作?修改、删除和新增,避免范围内的数据数量和数据本身发生变化。所以,数据的修改就需要通过记录锁record lock解决,而对于数据的新增和删除来说,我们需要锁住的不是记录,而是记录之间的间隙,避免插入(这个时候就需要next key)。
- 那么为什么需要有next key?正如我们前面说的,record lock和gap lock解决了记录本身和记录间隙之间的变化,而为了更加方便地实行加锁操作,就出现了next lock,它的本质是方便进行加锁,所以next lock成为了MySQL加锁的基本单位。
相关阅读:
说完了有哪些锁,那么实战的时候,他们到底是怎么加锁的呢?加锁的方式其实很简单,就是从第一个满足条件的地方开始加锁,直到第一个不满足条件的地方结束,通过next lock加锁。
而锁的优化机制主要有两个:都是因为等值查询的特殊情况。
- next lock退化成record lock:退化成record lock主要是因为在唯一索引下的等值查询,本质上就避免了出现第二个数据,所以根本就不需要锁间隙;
- next lock退化成gap lock:退化成gap lock主要是因为普通索引下的等值查询,在没有查到数据的情况下,由于无法保证数据的唯一性,需要通过锁间隙的方式,防止新增数据对等值查询造成的影响。而由于没有找到指定的值,那么就不需要使用记录锁锁住等值的部分,因为当最后一个数据不为目标数据a时,则说明最后一个数据b一定大于a,所以此时我们锁住区间,就已经锁住了等值查询的目标数据了,此时我们只需要防止区间插入即可,即gap lock。
ps:范围查询中如果有等值的情况,其实也属于等值查询。
相关阅读:
JDK锁机制
JDK锁机制主要分为乐观锁和悲观锁,而为了保证并发安全,本质上只需要保证三个核心特性,分别是可见性、原子性和有序性。
相关阅读:
- 并发安全的三大要素是什么?
- 乐观锁和悲观锁的区别是什么?
- 乐观锁和悲观锁的实现机制是什么?
乐观锁
首先是乐观锁,乐观锁从本质是通过CAS和volatile实现并发安全。
- CAS用于保证原子性,而volatile用于保证可见性,Java语言自身就可以保证有序性。
乐观锁的典型应用是AQS,AQS是一个同步器,核心是CLH队列,所有的线程通过接入AQS实现对共享资源的竞争,竞争失败的线程就会进入CLH队列中,继续参加日后的竞争。所以AQS的核心其实就是对于共享资源的竞争,而CAS+volatile就是竞争共享资源的核心。
相关阅读:
悲观锁
然后就是悲观锁,悲观锁主要有两种形式,分别是sychronized和reentrantlock。synchronized是java语言层面,而reentrantlock是lock接口层面的。
相关阅读:
synchronized
synchronized主要有三个特性,分别是公平性、可重入性、互斥性。想要知道synchronized的原理,其实只需要从两个方向出发,分别是Java对象头和Monitor。
- 对象头是synchronized的基础,因为synchronized申请锁、上锁、释放锁都与对象头有关;
- synchronized 基于进入和退出监视器对象(monitor)来实现方法同步和代码块同步,每个对象都存在着一个monitor与之关联,monitor对象存在于每个Java对象的对象头中(存储的指针的指向)。
相关阅读:
reentrantLock
Reentrantlock的核心就是对于AQS的使用。
相关阅读:
分布式锁
分布式锁的核心是唯一性,无论是MySQL、Redis和Zookeeper,想要实现分布式锁,本质上都是实现唯一性。而任何一个锁机制,其核心特点都是可重入性和互斥性。
- 唯一性,以MySQL实现分布式锁为例,存在并发创建key的问题,即有可能同时创建两个相同的key,解决方式就是使用唯一索引实现唯一性,避免存在两个相同的key。
- 互斥性、可重入性,利用value设置身份验证,实现可重入和互斥。
相关阅读: