# 锁
- 并发的安全性及可用性,必须通过一种机制来得到保证
- 锁机制,正是这样一种解决方案
# 悲观锁
- 常规意义上的锁,都是悲观锁
- 为了避免数据备同时修改,对一条数据进行修改前进行上锁,知道自己修改完,提交事务,才释放锁
# 乐观锁
- 相对悲观锁而言,不加锁,只是在进行提交更新的时候进行冲突检测
- 没有冲突,则正常更新数据,有冲突则由程序决定是否回滚等操作
- 高并发时,乐观锁会导致大量的失败,关注用户体验时,适合并发冲突(请求同时修改共享数据)比较少时使用,但可以通过服务降级等手段进行处理
# 应用级锁
# synchronized锁
是一种悲观锁
同一时刻,只有一个线程在执行当前代码;
本质上是在并行线程串行化;
# CAS锁
是一种乐观锁
体现在java的多个
Automic
类中,如AutomicInteger
由于CAS是CPU的原子指令,不会造成所谓的数据不一致问题;
通过自旋锁,实现锁的占用,即采用循环的方式去尝试获取锁,而不是线程阻塞,不会带来上下文切换,上下文切换是开销较大的一种操作
CAS是整个
java.util.concurrent
的基石,JUC中大量使用了CAS
# Lock锁
区别 | synchronized | Lock |
---|---|---|
存在层次 | java关键字,jvm层面 | 接口 |
锁的释放 | 执行完同步代码,自动释放; 线程异常,自动释放 | 必须在finally中释放锁,即必须手动释放锁 |
是否可中断 | 只能等待锁的释放,不能响应中断 | 可以用interrupt来中断等待 |
锁类型 | 可重入、不可中断、非公平 | 可重入、可中断、可公平 |
性能 | 适合少量并发同步 | 适合大量并发的同步,大并发时,Lock性能远远高于synchronized |
锁性质 | 悲观锁 | 乐观锁 |
线程调度 | 对象本身的wati,notify,notifyAll调度机制 | 使用Condition进行线程调度 |
优点 | 语义清晰 | 灵活、可实现一些synchronized的特性,如 中断、公平锁、读写分离等 |
# 缺点
- 如果自旋长时间不成功,可能造成CPU开销大问题,可通过限制自旋次数解决
- ABA(中间经过变化后,又变回原值)问题,可通过添加版本号,或者时间戳解决
- 只能保证一个共享变量原子操作,而不能保证代码块,可通过
synchronized
协助或者通过通过AtomicReference包装多个变量
# 分布式应用锁
# 中间件分布式锁
# redis锁
- 主要依赖
redis
的setnx
以及expire
这两个函数,setnx用于锁占位,expire用于超时,避免死锁; - 应用程序层面,通过调用该api实现:
boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "lock", 30, TimeUnit.SECONDS)
,如果获取不到值,还可以进行自旋;
# 数据库排他锁
- 通过切面+数据库的方式,执行前插入进程数据,插入成功则视为获取到了锁;
- 执行完毕后,需要删除数据,视为释放锁;
# 框架分布式锁-curator
- curator是apache顶级开源项目
- curator与
zk
的关系,就像guava与java的关系
# 使用zookeeper作为中间件锁
- 为某个方法加锁是,在
zk
上,为该方法生成一个唯一的临时有序节点 - 通过
InterProcessMutex
代表分布式锁,所有的操作基于它完成
# 数据库锁
# 悲观锁
通过数据库本身的锁机制实现的排他锁;
通过
for update
可实现悲观锁,是一个行级悲观锁,如select quantity from items where id=1 for update
,需要结合事务操作,有产生死锁的可能性;MySQL InnoDB默认行级锁。行级锁都是基于索引的,如果一条SQL语句用不到索引是不会使用行级锁的,会使用表级锁把整张表锁住
# 乐观锁
- 通过version字段进行控制
# 补充
- 随着互联网三高(高并发,高性能,高可用)架构,悲观锁已经越来越少被使用到生产环境中,尤其是并发量比较大的业务场景;
- 事务和锁不是一个概念
- 脏写是数据不能接受的,而只要有事务,便能解决这个问题;
mysql InnoDB
引擎默认的修改数据语句,update,delete,insert都会自动给涉及到的数据加上排他锁,select语句默认不会加任何锁类型,如果加排他锁可以使用select ...for update语句,加共享锁可以使用select ... lock in share mode语句
# 共享锁与排他锁
- 行级锁分为: 共享锁与排他锁
- 即锁是针对特定数据而言,而事务是针对一批操作而言
- 摘录于博客园-mysql共享锁与排他锁 (opens new window)
# 共享锁
- 又称为读锁,多个事务都可以共享数据,但是只能读,不能修改;
- 查询时,默认是不加锁的
- 有读锁的情况下,不能再加写锁;
# 排他锁
- 又称为写锁,其它事务不能再进行加锁
- 事务本身可以进行读取和修改操作;