并发锁分析

为什么加锁

  1. 并发会造成数据混乱

事务

  1. 原子性a
  2. 一致性c
  3. 隔离性i
  4. 持久性d

加锁的影响和优化点

  1. 线程切换的开销(缓存命中率)

    加锁失败会进行线程切换

  2. 用户态和内核态的切换开销(栈的切换/寄存器切换)

    synchronized重量锁的实现是 系统调用 会涉及到内核和用户态的切换(加锁成功和失败都会切换)

  3. cas保证原子性,避免系统调用
    cas 无系统调用

  4. 加锁粒度
    表->段->行

  5. 互斥到共享

锁存在的问题

  1. 死锁

指标和分类

  • 同步和异步(阻塞和非阻塞和带超时的阻塞)
  • 互斥和读写
  • 独占和共享
  • 公平和非公平
  • 可中断
  • 可重入
  • 分段
  • 自旋

锁的组成

  1. 共享资源 比如state 必须是所有线程共享的
  2. 临界区 产生竞争的一段代码 加锁和释放锁中间的操作(左闭右闭区间)
  3. 原子性 只有一个线程可以更改共享资源 比如state+1 state-1
  4. 可见性 volatile
  5. 互斥性

java

synchronized
对象头

https://www.cnblogs.com/hongdada/p/14087177.html

状态机

无锁->偏向锁->轻量锁(自旋锁)->重量锁
状态转换关系
在这里插入图片描述
线程1进入临界区,检测到同步对象处于无锁状态,将线程id拷贝到对象头,同步对象升级为偏向锁,线程1退出时,检查同步对象头是偏向锁,不对锁进行释放
线程1再次进入临界区,检测到偏向锁的线程id是自己的id, 获取锁成功,线程1退出时,检查同步对象头是偏向锁,不对锁进行释放
线程2进入临界区,检测到偏向锁的线程id不是自己的id, 获取锁失败,将锁升级为轻量级锁(更新同步对象头的字段 设置是否为偏向锁为否),线程2进入循环获取锁,n次(可配置 默认15)
线程1退出时,判断同步对象头不是偏向锁,要把锁释放为无锁,此时循环中的线程2,可以获取到锁,并且设置为偏向锁(此时偏向的是线程2)
如果线程2在循环15层次之后,仍然没能获取到锁,那么进入阻塞,同步对象升级为重量锁(更新同步对象头,设置锁指针为一个监视器对象),
线程3进入临界区,检测对象头,已经是轻量锁,升级锁为重量锁,

线程释放锁时,检查同步对象头,
如果是偏向锁,不做任何处理,直接退出
如果是轻量锁,要把同步对象头中的偏向线程id给去掉(检查是不是自己的),轻量锁线程id给去掉,把同步对象置为无锁状态
如果是重量锁,要把同步对象中的偏向线程id给去掉,轻量锁线程id给去掉,检查重量锁的监视器对象是否还有等待状态的线程,进行唤醒,如果没有,把重量锁的监视器指针给去掉,把锁置为无锁状态

AQS
  1. 可中断
  2. state和等待队列
  3. 基于LockSupport实现
    在这里插入图片描述
    在这里插入图片描述
    park和unpark实现线程的阻塞和唤醒,底层又是通过UNSAFE实现
  4. . 加锁失败,即cas更新共享资源state失败,加入等待队列,调用park,阻塞当前线程。
    锁释放的时候,从当前阻塞队列中获取一个或全部进行唤醒
concurrenthashmap
java7

段锁

java8

行锁
CAS原子操作 链表为空插入第一个节点时是原子操作
synchronized 插入链表非第一个节点时 使用synchronized 锁住链表头节点

mysql

表锁

myisam
update where sex = 1(sex列不是索引)

行锁

innodb
依赖索引
update where index=1 (index是索引列)

间隙锁

(负无穷,a)(a,b)(b,无穷)
update where index > 2(index是索引列)
update where index<1
update where index<1 and index ❤️

乐观锁

修改时再加锁
select 不加锁
update 加锁

悲观锁

提前加锁
select for update

分布式锁

同单体应用的锁,分布式锁也需要共享资源来实现锁,共享资源需要由第三方中间件来实现,比如redis,zookeeper,mysql等
分布式锁一般是非阻塞的
要实现阻塞,需要自旋

redis

通过 nx 参数来实现CAS互斥
通过 ex 参数实现自动过期

加锁流程

  1. client1首先加锁,发现key的值为null,设置key为1 cas(key,null,1)
  2. client2其次加锁,发现key的值为1,加锁失败 cas(key,null,1)

解锁流程

  1. client1解锁,把key设置为null,或者删除key
  2. client2重试,发现key的值为null,加锁成功
  3. key超时,redis自动清除key,client2重试,发现key的值为null,加锁成功

特性:

  1. 可以通过ex 参数来设置有效期

    优点: 保证不会产生死锁,客户端宕机之后,锁不会永久存在
    缺点: 可能会在解锁之前就失效,过期时间不好确定
    
  2. 内存操作,性能高,持久性由redis保证

  3. redis保证集群的高可用

  4. 没办法阻塞,客户端只能自旋循环检测加锁

缺点:

  1. 不能实现公平锁,先阻塞的不一定先唤醒

zookeeper

临时节点实现自动过期

临时顺序节点实现互斥

watch机制实现阻塞队列

加锁流程:

  1. client1 在创建lock1临时节点
  2. client2 发现已经存在lock1临时节点 加锁失败,创建lock2临时节点,并watch lock1
  3. client3 发现已经存在lock2临时节点,加锁失败,创建lock3临时节点,并watch lock2

解锁流程

  1. client1删除lock1临时节点,client2收到lock1节点删除事件,client2唤醒.
  2. client2宕机,事务失败,lock1临时节点删除,client2收到lock1节点删除事件,client2唤醒

mysql

  1. 悲观锁

通过 select for update 实现悲观锁

  1. 乐观锁

v e r = s e l e c t v e r f r o m x x x ;   r o w s = u p d a t e x x x w h e r e v e r = ver=select ver from xxx;   rows=update xxx where ver= ver=selectverfromxxx; rows=updatexxxwherever=ver;  
rows==0 表示更新失败,表示加锁失败  
rows>0 表示加锁成功

参考来源

  1. https://blog.csdn.net/qq_19007169/article/details/124591890
  2. JAVA 对象头分析及Synchronized锁 - hongdada - 博客园 (cnblogs.com)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值