多线程基础3(锁策略)

常见锁策略

  1. 乐观锁VS悲观锁

  • 乐观锁:预测锁冲突的概率不高;

  • 悲观锁:预测锁冲突的概率较高;

  1. 读写锁VS普通互斥锁

  • 读写锁

  1. 加读锁

  1. 加写锁

  1. 同时读不会有线程安全问题,同时写、同时读和写都存在线程安全问题,读写锁对读和写区分加锁,相较于普通的互斥锁就会少了很多锁竞争,优化了效率。

  • 普通互斥锁:例如synchronized,当两个线程竞争一把锁,就会产生等待。

  1. 重量级锁VS轻量级锁

  • 重量级锁:加锁解锁开销比较大(进入内核态的加锁逻辑);

  • 轻量级锁:加锁解锁开销较小(纯用户态的加锁逻辑)。

  1. 自旋锁VS挂起等待锁

  • 自旋锁:是轻量级锁的一种典型实现;(同时也是一个乐观锁)

  1. 优点:持续等待,一旦锁被释放,第一时间就能获取锁

  1. 缺点:CPU占用率较高

  • 挂起等待锁:是重量级锁的一种典型实现

  1. 优点:不占用资源,资源可以得到合理利用

  1. 缺点:获取锁时间不及时

  1. 公平锁VS非公平锁

  • 公平锁:遵守先来后到获取锁;(实现公平锁需要引入额外的开销用来记录线程加锁的顺序)

  • 非公平锁:不遵守先来后到,随机抢占。

  1. 可重入锁VS不可重入锁

  • 可重入锁:针对同一个线程连续加锁两次不会死锁;

  • 不可重入锁:针对同一个线程连续加锁两次会死锁。

  1. synchronized

  • 既是乐观锁也是悲观锁;

  • 既是轻量级锁也是重量级锁;

  • 乐观锁部分是基于自旋锁实现的,悲观锁部分是基于挂起等待锁实现的;

  • 以上三种状态是根据锁竞争环境来看,初始时锁竞争不激烈则都是前者状态,如果锁竞争激烈就变为后者的状态。

  • 不是读写锁,是普通互斥锁;

  • 非公平锁;

  • 可重入锁。(synchronized关键字锁都是可重入的)

CAS(compare and swap)

  1. 如何操作

  • 内存中原数据、旧的预期值、需要修改的新值;

  • 比较旧的预期值与原数据是否相等;

  • 若相等则把需要修改的新值与原数据进行交换;

  • 返回操作是否成功。

  1. 应用场景

  • 实现原子类:

CAS通过比较交换进行循环,需需要使用轻量级锁就能完成多线程的自增工作,实现一个原子类。

  • 实现自旋锁:

判断CAS是否进行交换,当返回操作为false时,会进行循环,立即再次发起判断

  1. CAS的ABA问题

  • ABA问题:在CAS中,进行比较的时候,寄存器和内存的值相同,但是无法判断内存的值是始终没变还是变了又变回来了。(极端情况下出现的bug:付费时网络卡顿多操作两次,此时就会有两个线程进行扣钱,基于CAS来扣钱,此时另外一个人转账同样的钱,扣完款账户余额没有发生变化,因此会继续扣钱,导致一次付费扣两份的钱。)

  • 解决办法:对内存数据变化进行记录,记录修改次数或上次修改时间,CAS与修改次数或上次修改时间进行比较就不会相同。

synchronized原理

  1. 加锁具体过程(锁升级/锁膨胀)

根据具体依次进行升级:

  • 偏向锁(无竞争)

  1. 不是真正加锁,只是设置了一个状态。

  1. 类似于懒汉模式,必要的时候再加锁;如果不是发生锁竞争,就没必要真加锁。

  • 轻量级锁(有竞争)

  • 重量级锁(竞争激烈)

  1. 锁消除

  • JVM自动判定,如果代码不必加锁就会自动把锁去掉;

  • 只有一个线程或者多个线程不修改一个变量,此时也会将锁去掉;

  • 锁消除是一种编译优化行为,不是一定准确,因此不确定时不一定会消除。

  1. 锁粗化

  • 锁的粒度:synchronized对应的代码块少(粒度细),多(粒度粗);

  • 锁粗化:细粒度加锁->粗粒度加锁;

  • 加锁解锁加锁解锁->加锁解锁;(加锁里面的代码变多了,对同一个对象加锁才能粗化)

  • 锁粗化避免频繁加锁解锁形成的锁竞争。

JUC

  1. Callable接口

  • 类似于Runnable;

  • Runnable描述任务不带返回值,Callable描述任务带返回值;

  • Callable 通常需要搭配 FutureTask 来使用;

  • FutureTask 用来保存 Callable 的返回结果. 因为Callable 往往是在另一个线程中执行的, 执行完毕时间并不确定,FutureTask 负责这个等待结果出来的工作.

  1. ReentrantLock

  • lock():加锁,获取不到一直等待;

  • unlock():解锁,容易遗忘;

  • 相对于synchronized的优势:

  1. tryLock():超时等待,等待一定的时间就放弃;

  1. 可以实现公平锁,进行简单传参就变为公平锁:

  1. synchronized是搭配wait/notify实现等待通知机制,唤醒是随机唤醒一个线程;reentrantLock搭配Condition类实现,唤醒操作时可以指定唤醒哪个线程的。

  1. 原子类

  • Java中封装好的,可以直接使用;

  • 使用场景多为多线程计数,可以通过原子变量进行累加。

  1. 线程池(前面写道过)

  1. Semaphore信号量

  • 信号量, 用来表示 "可用资源的个数". 本质上就是一个计数器;

  • 信号量是更广义的锁,所示特殊的信号量(可用资源只有1的信号量);

  • 当需求中有多个可用资源就可以使用信号量。

  1. CountDownLatch

  • 构造 CountDownLatch 实例, 初始化 10就表示有 10 个任务需要完成;

  • 每个任务执行完毕, 调用 latch.countDown();

  • 在 CountDownLatch 内部的计数器同时自减;

  • 主线程中使用 latch.await(); 阻塞等待所有任务执行完毕。(相当于计数器为 0)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值