0
点赞
收藏
分享

微信扫一扫

多线程进阶上

福福福福福福福福福 2022-04-01 阅读 88

常见锁策略

锁策略和程序员无关,和"实现锁"的人才有关系

所提及到的锁策略,和Java本身没有关系,适用于所有和"锁"相关的情况.

悲观锁 vs 乐观锁(处理锁冲突的原因)

悲观锁:预期所冲突的概率很高

乐观锁:预期锁冲突的概率很低

读写锁 vs普通的互斥锁

对于普通的互斥锁,只有两个操作 加锁和解锁

只要两个线程针对同一个对象加锁,就会产生互斥

针对读写锁:

  • 针对读锁和读锁之间,是不存在互斥的(多线程同时同一个变量,不会有线程安全问题)
  • 针对读锁和写作之间,存在互斥
  • 针对写锁和写锁之间,存在互斥

重量级锁 vs轻量级锁(处理所冲突的结果)

重量级锁,就是做的事情比较多,开销更大

轻量级锁,就是做的事情比较少,开销更小

在一般情况下,不绝对的情况下

再使用的锁中,如果锁是基于内核的一些功能来实现的(比如调用了操作系统提供的mutex接口)此时一般认为是重量级锁(操作系统的锁会在内核做很多的事情,比如线程等待…)

如果锁是纯用户态实现的,此时一般认为是轻量级锁(用户态的代码更可控,更高效)

挂起等待锁 vs 自旋锁

挂起等待锁 , 往往就是通过内核的一些机制来实现的,往往较重 (重量级锁的一种典型实现)

自旋锁 , 往往就是通过用户态代码实现的 (轻量级锁的一种典型实现)

公平锁 vs非公平锁

公平锁:多线程在等待同一把锁的时候,谁是先来的,谁可以获得这把锁(先来后到原则)

非公平锁: 多线程在等待同一把锁的时候,不遵循先来后到(每个等待的线程获取到锁的概率是均等的)

可重入锁 vs 不可重入锁

一个线程针对同一个锁,连续加锁两次,如果会死锁,就是不可重入锁;如果不会发生死锁,就是可重入锁

synchronized的锁策略

CAS

CAS: 全称Compare and swap,字面意思:”比较并交换“,一个 CAS 涉及到以下操作:

CAS伪代码

boolean CAS(address, expectValue, swapValue) {
 if (&address == expectedValue) {
   &address = swapValue;
        return true;
   }
    return false; 
}

CAS最大的意义就是为我们写多线程安全的代码,提供了新的思路和方向

CAS的应用

实现原子类

Java标准库中提供了一组原子类,针对所常用的依稀int,long.int array…进行一封装,可以基于CAS的方式进行修改,并且线程安全

image-20220330184800394

伪代码实现

class AtomicInteger {
    private int value;
    public int getAndIncrement() {
        int oldValue = value;
        while ( CAS(value, oldValue, oldValue+1) != true) {
            oldValue = value;
       }
        return oldValue;
   }
}

实现简图:
image-20220330190417305

基于CAS实现"自旋锁"

伪代码

public class SpinLock {
    private Thread owner = null;
    public void lock(){
        // 通过 CAS 看当前锁是否被某个线程持有. 
        // 如果这个锁已经被别的线程持有, 那么就自旋等待. 
        // 如果这个锁没有被别的线程持有, 那么就把 owner 设为当前尝试加锁的线程. 
        while(!CAS(this.owner, null, Thread.currentThread())){
       }
   }
    public void unlock (){
        this.owner = null;
   }
}

CAS的ABA问题(重要)

CAS的关键就是 先比较 在交换

比较其实是在比较当前值 和旧值是不是相同,把这两个值相同,就是视为中间没有发生过改变.

关于ABA问题的实例

假如我有的账户有100元,这时我去ATM机准备取50元,但当我按下取款按钮的时候,机器卡了,我就多按了一下取款按钮.这相当于一次取钱操作,执行了两次(两个线程,并发的去执行这个操作),期望的是只取成功一次

基于CAS对的方式实现这里的取款操作

ind oldValue = value;//读取旧值
CAS(&value,oldValue,oldValue-50)

image-20220330193313304

此时没有发生ABA问题,但是加一个条件,就是朋友这个时候转过来了50元钱,就会发生ABA

image-20220330193518559

可以看到,在执行t2的cas操作之前,value的值和oldValue的值相同,所以还要继续扣50,这就产生了安全问题.

解决方案

给要修改的值, 引入版本号. 这个版本号只能变大,不能变小,修改变量的时候,比较的不是变量本身,而是比较版本号.

CAS 操作在读取旧值的同时, 也要读取版本号.

  • 真正修改的时候,
    • 如果当前版本号和读到的版本号相同, 则修改数据, 并把版本号 + 1.
    • 如果当前版本号高于读到的版本号. 就操作失败(认为数据已经被修改过了).

image-20220401141139838

不光是可以插入版本号,只要是一个单调递增或者单调递减的数据就可以,比如时间戳

举报

相关推荐

0 条评论