死锁和CAS

文章介绍了死锁的概念,包括产生条件和三种场景,并通过代码示例展示了死锁的产生。接着讲解了CAS(CompareandSwap)操作,它是无锁编程的一种实现,用于在不加锁的情况下保证线程安全,同时提到了CAS可能导致的ABA问题及其解决方案。
摘要由CSDN通过智能技术生成

一、死锁

1.什么是死锁

死锁是指由于两个或者两个以上的线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法继续执行。

甲和乙两名同学做卫生,但是只有一把扫帚和簸箕。甲同学拿到了扫帚,乙同学拿到了簸箕,此时甲同学要等乙同学的簸箕才能做卫生,而乙同学要等甲同学的扫帚才能做卫生。这个时候就形成了僵局,也就是死锁。

2.死锁产生条件

  • 互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用

    • 甲拿到扫帚,乙就不可能拿到扫帚
  • 不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。

    • 甲虽然很需要簸箕,但是不能直接抢过来只能等乙自己给甲
  • 请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。

    • 甲虽然没有簸箕导致不能做卫生,但也不能主动把扫帚给乙
  • 循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路

    • 甲拿着扫帚在等乙的簸箕,而乙拿着簸箕等甲的扫帚,形成闭环

3.死锁的三种场景

3.1 一个线程,针对同一把锁

public static void main(String[] args) {
    Object lock = new Object();
   Thread t = new Thread(()->{
       synchronized (lock){
           synchronized (lock){

           }
       }
   });
}

t线程针对lock这个锁对象进行了两次加锁操作

如果是可重入锁,不会产生死锁,如果不是可重入锁,产生死锁。

3.2 多个线程,针对同一把锁

    public static void main(String[] args) {
        Object lock1 = new Object();
        Object lock2 = new Object();
        //t1线程先抢lock1,再抢lock2
        Thread t1 = new Thread(()->{
            synchronized (lock1){
                synchronized(lock2){
                    
                }
            }
        });
        //t2线程先抢lock2,再抢lock1
        Thread t2 = new Thread(()->{
            synchronized (lock2){
                synchronized(lock1){

                }
            }
        });
    }

t1占用了lock1,t2占用了lock2,当t1想再去占用lock2时,发现lock2已经被占用,同理t2也不能占用lock1,就形成了死锁。

这种情况即即使是可重入锁也会发生死锁

3.3 多个线程,针对多个锁

哲学家就餐问题

假设有五位哲学家围坐在一张圆形餐桌旁,做以下两件事情之一:吃饭,或者思考。吃东西的时候,他们就停止思考,思考的时候也停止吃东西。餐桌中间有五碗意大利面,每两个哲学家之间有一只餐叉。因为用一只餐叉很难吃到意大利面,所以假设哲学家必须用两只餐叉吃东西。他们只能使用自己左右手边的那两只餐叉。

哲学家就餐

如果所有哲学家都抢到了左边的餐叉,再去抢右边的餐叉发现已经被抢了,且所有哲学家都不会放在已经抢到的餐叉,只会等待有右边的餐叉,这就形成了死锁

打破任意一个产生死锁的条件就能解决问题,其中循环等待是最容易打破的

解决办法:给所有资源编号,规定进程请求所需资源的顺序必须按照一定的约定执行

比如我们约定,只能从编号小的锁开始加锁

public static void main(String[] args) {
    Object lock1 = new Object();
    Object lock2 = new Object();
    Thread t1 = new Thread(()->{
        synchronized (lock1){
            synchronized(lock2){
                
            }
        }
    });
    Thread t2 = new Thread(()->{
        synchronized (lock1){
            synchronized(lock2){

            }
        }
    });
}

约定了抢占锁的顺序之后,就可以避免死锁了。

二、CAS

1.什么是CAS

CAS: Compare and swap,寄存器A中的值如果和内存中M的值相等,就把寄存器B中的值和M的值进行交换
在这里插入图片描述

  1. 比较 A 与 V 是否相等。(比较)

  2. 如果比较相等,将 B 写入 V。(交换)

  3. 返回操作是否成功。

虽然CAS中既有比较,也有赋值操作,但CAS仍然属于一条CPU指令【原子性】

伪代码:

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

**CAS作用:**CAS可以在不加锁的情况下保证线程的安全

2.CAS应用

2.1实现原子类

标准库中提供了 java.util.concurrent.atomic 包, 里面的类都是基于这种方式来实现的。典型的就是 AtomicInteger 类,这个类能够保证执行加或者减的时候线程安全。

AtomicInteger伪代码

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

oldValue:表示寄存器的值

( CAS(value, oldValue, oldValue+1) != true : 如果内存中的value和寄存器中的oldValue相等,就把oldValue++,然后返回true,循环结束;如果不相等,返回false,进入循环,重新设置oldValue的值。

注意:oldValue++之后应该返回true,但是在多线程环境下,可能会返回false。CAS能保证线程的安全,就是因为它会判断oldValue是否发生过变化,如果发生了会先更新再比较

2.2 实现自旋锁

伪代码:

public class SpinLock {
	//ownerv用来记录当前锁被那个线程所持有
    private Thread ownerv = null;
    public void lock(){
    //如果owner为null,那就比较成功,然后当前线程就持有这个锁
    //如果owner不为null,比较失败,返回false,继续执行循环
    //循环执行速度很快,一旦ownerv没有被其他线程持有,马上就能获取到
    while(!CAS(this.owner, null, Thread.currentThread())){
    
    }
   }
    public void unlock (){
        this.owner = null;
   }
}

注意:CAS本身就是指令级别,属于读取内存的操作,不存在编译器优化导致内存可见性

3.CAS的ABA问题

CAS做的事情是比较内存中的值前后是否相等,但不能判断中间过程是否发生过变化。a->b->aCAS是能通过的,这就引出了CAS中的ABA问题。

比如你买了一个手机,这个手机有可能是纯新机,有可能是翻新机,一个普通用户区分不了就认为翻新机也是纯新机。

解决办法:使用版本号,同时约定版本号自增,最后比较的时候就去比较版本号,只要版本号不一样,就认为是发生了变化

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值