Java并发编程—CAS、死锁

Java并发编程—CAS、死锁

它的英文全称是 Compare-And-Swap,中文叫做“比较并交换”,它是一种思想、一种算法。

在多线程的情况下,各个代码的执行顺序是不能确定的,所以为了保证并发安全,我们可以使用互斥锁。而 CAS 的特点是避免使用互斥锁,当多个线程同时使用 CAS 更新同一个变量时,只有其中一个线程能够操作成功,而其他线程都会更新失败。不过和同步互斥锁不同的是,更新失败的线程并不会被阻塞,而是被告知这次由于竞争而导致的操作失败,但还可以再次尝试。

CAS 被广泛应用在并发编程领域中,以实现那些不会被打断的数据交换操作,从而就实现了无锁的线程安全。

CAS 有三个操作数:内存值 V、预期值 A、要修改的值 B。CAS 最核心的思路就是,仅当预期值 A 和当前的内存值 V 相同时,才将内存值修改为 B。

CAS常见的使用场景

1)并发容器

2)数据库

3)原子类

CAS的缺点
ABA问题

​ 有的业务场景下,我们想确切知道从上一次看到这个值以来到现在,这个值是否发生过变化。例如,这个值假设从 A 变成了 B,再由 B 变回了 A,此时,我们不仅认为它发生了变化,并且会认为它变化了两次。所以,CAS 并不能检测出在此期间值是不是被修改过,它只能检查出现在的值和最初的值是不是一样。

​ 在 atomic 包中提供了 AtomicStampedReference 这个类,它是专门用来解决 ABA 问题的,解决思路正是利用版本号,AtomicStampedReference 会维护一种类似 <Object,int> 的数据结构,其中的 int 就是用于计数的,也就是版本号,它可以对这个对象和 int 版本号同时进行原子更新,从而也就解决了 ABA 问题。因为我们去判断它是否被修改过,不再是以值是否发生变化为标准,而是以版本号是否变化为标准,即使值一样,它们的版本号也是不同的。

自旋时间过长

​ 由于单次 CAS 不一定能执行成功,所以 CAS 往往是配合着循环来实现的,有的时候甚至是死循环,不停地进行重试,直到线程竞争不激烈的时候,才能修改成功。

可是如果我们的应用场景本身就是高并发的场景,就有可能导致 CAS 一直都操作不成功,这样的话,循环时间就会越来越长。而且在此期间,CPU 资源也是一直在被消耗的,这会对性能产生很大的影响。所以这就要求我们,要根据实际情况来选择是否使用 CAS,在高并发的场景下,通常 CAS 的效率是不高的。

范围不能灵活控制

​ 通常我们去执行 CAS 的时候,是针对某一个,而不是多个共享变量的,这个变量可能是 Integer 类型,也有可能是 Long 类型、对象类型等等,但是我们不能针对多个共享变量同时进行 CAS 操作,因为这多个变量之间是独立的,简单的把原子操作组合到一起,并不具备原子性。因此如果我们想对多个对象同时进行 CAS 操作并想保证线程安全的话,是比较困难的。

有一个解决方案,那就是利用一个新的类,来整合刚才这一组共享变量,这个新的类中的多个成员变量就是刚才的那多个共享变量,然后再利用 atomic 包中的 AtomicReference 来把这个新对象整体进行 CAS 操作,这样就可以保证线程安全。

死锁

​ 死锁是一种状态,当两个(或多个)线程(或进程)相互持有对方所需要的资源,却又都不主动释放自己手中所持有的资源,导致大家都获取不到自己想要的资源,所有相关的线程(或进程)都无法继续往下执行,在未改变这种状态之前都不能向前推进,我们就把这种状态称为死锁状态,认为它们发生了死锁。通俗的讲,死锁就是两个或多个线程(或进程)被无限期地阻塞,相互等待对方手中资源的一种状态。

发生死锁的4个条件
  • 第 1 个叫互斥条件,它的意思是每个资源每次只能被一个线程(或进程,下同)使用,为什么资源不能同时被多个线程或进程使用呢?这是因为如果每个人都可以拿到想要的资源,那就不需要等待,所以是不可能发生死锁的。
  • 第 2 个是请求与保持条件,它是指当一个线程因请求资源而阻塞时,则需对已获得的资源保持不放。如果在请求资源时阻塞了,并且会自动释放手中资源(例如锁)的话,那别人自然就能拿到我刚才释放的资源,也就不会形成死锁。
  • 第 3 个是不剥夺条件,它是指线程已获得的资源,在未使用完之前,不会被强行剥夺。比如我们在上一课时中介绍的数据库的例子,它就有可能去强行剥夺某一个事务所持有的资源,这样就不会发生死锁了。所以要想发生死锁,必须满足不剥夺条件,也就是说当现在的线程获得了某一个资源后,别人就不能来剥夺这个资源,这才有可能形成死锁。
  • 第 4 个是循环等待条件,只有若干线程之间形成一种头尾相接的循环等待资源关系时,才有可能形成死锁,比如在两个线程之间,这种“循环等待”就意味着它们互相持有对方所需的资源、互相等待;而在三个或更多线程中,则需要形成环路,例如依次请求下一个线程已持有的资源等。
定位死锁
  • jstask

    这个命令叫作 jstack,它能看到我们 Java 线程的一些相关信息。如果是比较明显的死锁关系,那么这个工具就可以直接检测出来;如果死锁不明显,那么它无法直接检测出来,不过我们也可以借此来分析线程状态,进而就可以发现锁的相互依赖关系,所以这也是很有利于我们找到死锁的方式。

  • ThreadMXBean类

    通过 ThreadMXBean 的 findDeadlockedThreads 方法,可以获取到一个 deadlockedThreads 的数组,然后进行判断,当这个数组不为空且长度大于 0 的时候

死锁常见的修复策略
  • 避免策略,避免策略最主要的思路就是,优化代码逻辑,从根本上消除发生死锁的可能性。
  • 检测与恢复策略,先允许系统发生死锁,然后再解除。
  • 鸵鸟策略,如果我们的系统发生死锁的概率不高,并且一旦发生其后果不是特别严重的话,我们就可以选择先忽略它。直到死锁发生的时候,我们再人工修复
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 《Java并发编程的艺术》是一本经典的Java并发编程指南。该书由翟陆续著,是针对Java并发编程的实用指南。该书内容详尽全面,从基础概念、线程安全到高级主题都进行了深入讲解。 该书首先介绍了Java并发编程的基本概念,如线程、线程安全等。然后详细讲解了Java中的各种并发类,如线程池、锁、原子类等,以及它们的使用方法和注意事项。 该书还探讨了Java中的常见并发问题,如死锁、活跃性问题等,并提供了解决方案。同时,该书还介绍了一些常用的并发编程模式,如生产者消费者模式、读写锁模式等,帮助读者更好地理解并发编程的应用场景。 除了基础知识和常见问题,该书还介绍了一些高级主题,如并发集合类、并发编程工具等。这些内容对于进一步提高并发编程的效率和性能非常有帮助。 总体来说,《Java并发编程的艺术》是一本深入浅出的Java并发编程指南。它详细讲解了Java中的并发编程概念、类库和常见问题,并给出了解决方案。读者通过学习该书可以深入了解Java并发编程,提高代码的性能和效率。无论是新手还是有经验的Java开发者,都可以从中受益匪浅。 ### 回答2: 《Java并发编程的艺术》是一本介绍Java多线程编程的经典著作,它涵盖了多线程并发编程相关的基础理论知识、实践经验和常用技巧,并提供了大量的示例代码和案例分析。在这本书中,作者通过深入浅出的方式,帮助读者理解Java多线程编程的核心概念和原理,并教授如何正确、高效地编写并发程序。 这本书从Java并发编程的背景和意义入手,介绍了线程的基本概念、线程的状态转换、线程同步与互斥、线程间通信等基础知识。然后,作者逐步展示了如何使用Java提供的并发工具类来实现并发任务的协调与管理,包括使用Lock和Condition进行底层同步、使用Future和Callable实现异步计算、使用线程池实现任务调度和资源管理等。 此外,书中还详细介绍了Java并发中的一些常见问题和挑战,例如线程安全、死锁、活锁等,作者通过实际案例向读者展示了如何识别和解决这些问题,以及如何通过优化设计和编码手段提高并发程序的性能。 总的来说,《Java并发编程的艺术》通过结合理论和实践,深入浅出地讲解了Java多线程编程的方方面面。对于想要深入了解Java多线程编程的人来说,这本书提供了非常有价值的指导和参考。无论是初学者还是有一定经验的开发者,都能从中获得知识和实践的结合,提高自己在并发编程领域的能力。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值