#

  • 并发的安全性及可用性,必须通过一种机制来得到保证
  • 锁机制,正是这样一种解决方案

# 悲观锁

  • 常规意义上的锁,都是悲观锁
  • 为了避免数据备同时修改,对一条数据进行修改前进行上锁,知道自己修改完,提交事务,才释放锁

# 乐观锁

  • 相对悲观锁而言,不加锁,只是在进行提交更新的时候进行冲突检测
  • 没有冲突,则正常更新数据,有冲突则由程序决定是否回滚等操作
  • 高并发时,乐观锁会导致大量的失败,关注用户体验时,适合并发冲突(请求同时修改共享数据)比较少时使用,但可以通过服务降级等手段进行处理

# 应用级锁

# 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锁

  • 主要依赖redissetnx以及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语句

# 共享锁与排他锁

# 共享锁

  • 又称为读锁,多个事务都可以共享数据,但是只能读,不能修改;
  • 查询时,默认是不加锁的
  • 有读锁的情况下,不能再加写锁;

# 排他锁

  • 又称为写锁,其它事务不能再进行加锁
  • 事务本身可以进行读取和修改操作;