线程六

75 阅读4分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

常见“锁”策略

这个是我们在实现锁的时候要考虑到的问题,这个是和java没有关系的,适用于所有和锁有关的情况

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

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

  2. 读写锁: 有三个操作,加读锁(如果代码只进行读,就加锁)、加写锁(如果代码进行修改操作,就加锁)、解锁

    ReentrantReadWriteLock.ReadLock
    ReentrantReadWriteLock.WriteLock
    

    普通互斥锁: 只有两个操作,加锁和解锁

  3. 重量级锁: 做了更多的事情,开销更大

    轻量级锁: 做的事情更少,开销更小

通常情况下面,悲观锁是重量级锁,乐观锁是轻量级锁)

  在使用锁中,如果锁是基于内核的一些功能来实现的(比如调用了操作系统提供的mutex接口),
  此时一般认为是重量级锁(操作系统的锁会在内核中做很多的事情)
  如果锁是纯用户态实现的,此时一般是轻量级锁
  (用户态的代码更可控,也更高效)
  

4.挂起等待锁: 往往就是通过内核的一些机制来实现的(重量级锁的实现)

自旋锁: 往往是用户态实现的。(轻量级锁的实现)

5.公平锁: 多个线程在等待一把锁的时候,谁先来谁就获得这把锁

非公平锁: 多个线程在等待一把锁的时候,谁来获得的概率都是平等的。(操作系统提供的mutex就是非公平锁)

6.可重入锁: 同一个线程,可以重复上一个锁两次

不可重入锁: 同一个线程,重复上一把锁两次会死锁

synchronized

1.既是一把乐观锁也是一把悲观锁(根据锁竞争的激烈程度,自适应)
2.不是读写锁,只是一个普通互斥锁
3.既是一个轻量级锁也是一个重量级锁(根据锁竞争的激烈程度,自适应)
4.轻量级锁的部分基于自旋锁来实现,重量级锁的部分基于挂起等待锁实现
5.非公平锁
6.可重入锁

CAS

拿着寄存器或者内存中的值和lingwai一个内存的值进行比较,如果值相同,就把另一个寄存器或者内存的值,和当前的这个内存交换。

功能:
我们假设内存中有一个值,预期认为这个值是A,现在我们希望这个值改成B
那我们先从这个内存里面取出这个值和A比较,如果一样,那我们就把A变成B,返回true
如果不一样就返回false

此处所谓的CAS指的是,CPU提供l一个单独的CAS指令,通过这个指令,就能完成上述功能

CAS提供了一个不用锁,但是依旧线程安全的思路

CAS如何帮助我们解决一些线程安全问题

1.基于CAS能够实现“原子类”

public static void main(String[] args) throws InterruptedException {
    AtomicInteger num = new AtomicInteger(0);
    Thread t1 = new Thread(()->{
       for(int i = 0;i<500;i++){
           num.getAndIncrement();
       }
    });
    Thread t2 = new Thread(()->{
        for(int i = 0;i<500;i++){
            num.getAndIncrement();
        }
    });
    t1.start();
    t2.start();
    t1.join();
    t2.join();
    System.out.println(num.get());
}

我们可以发现,这个和我们之前做的一个不一样。都是让一个元素自增1000次,但是这个是正确的,以前的那个要是不加锁就是不行的。

2.基于CAS能实现自旋锁

CAS中的ABA问题

CAS的关键是先比较再交换

如果比较之后发现值相同就认为是没有改变,但是这个是有一个缺陷的,有可能这是是变过之后又变回去的。

就是A变成了B然后又变成了A,这个时候去比较的时候,CAS会认为这个是没有改变过的,这个有可能会引发bug。

举个例子:

比如有个人有200,然后去取钱,打算取100,这个时候机器卡了一下,那个人呢,就多按了一下,这个时候就导致有两个线程在操作了,很不巧的是,这个人的朋友给他转账100,这个时候就有可能触发ABA。

第一个线程取出100,这个时候账户里面还有100,在多按那一下的线程的CAS还没有开始的时候,朋友转账的钱到户了,导致100又变成了200,这个时候,CAS看了一下内存里面的数值还是200,就认为这个钱还没有取出来,就把钱又取出来了。那我们就多取了100出来。

解决CAS中的ABA问题

引入一个版本号就可以了,根据版本号来判断这个值是否被改变过。