07 | 安全性、活跃性以及性能问题
王宝令
该思维导图由 AI 生成,仅供参考
通过前面六篇文章,我们开启了一个简单的并发旅程,相信现在你对并发编程需要注意的问题已经有了更深入的理解,这是一个很大的进步,正所谓只有发现问题,才能解决问题。但是前面六篇文章的知识点可能还是有点分散,所以是时候将其总结一下了。
并发编程中我们需要注意的问题有很多,很庆幸前人已经帮我们总结过了,主要有三个方面,分别是:安全性问题、活跃性问题和性能问题。下面我就来一一介绍这些问题。
安全性问题
相信你一定听说过类似这样的描述:这个方法不是线程安全的,这个类不是线程安全的,等等。
那什么是线程安全呢?其实本质上就是正确性,而正确性的含义就是程序按照我们期望的执行,不要让我们感到意外。在第一篇《可见性、原子性和有序性问题:并发编程 Bug 的源头》中,我们已经见识过很多诡异的 Bug,都是出乎我们预料的,它们都没有按照我们期望的执行。
那如何才能写出线程安全的程序呢?第一篇文章中已经介绍了并发 Bug 的三个主要源头:原子性问题、可见性问题和有序性问题。也就是说,理论上线程安全的程序,就要避免出现原子性问题、可见性问题和有序性问题。
那是不是所有的代码都需要认真分析一遍是否存在这三个问题呢?当然不是,其实只有一种情况需要:存在共享数据并且该数据会发生变化,通俗地讲就是有多个线程会同时读写同一数据。那如果能够做到不共享数据或者数据状态不发生变化,不就能够保证线程的安全性了嘛。有不少技术方案都是基于这个理论的,例如线程本地存储(Thread Local Storage,TLS)、不变模式等等,后面我会详细介绍相关的技术方案是如何在 Java 语言中实现的。
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
- 深入了解
- 翻译
- 解释
- 总结
并发编程中的安全性、活跃性和性能问题是关注的核心议题。安全性问题包括原子性、可见性和有序性问题,需要避免数据竞争和竞态条件,可通过锁机制解决。活跃性问题涉及死锁、活锁和饥饿,需要采取相应措施避免线程无法执行下去的情况。性能问题中提到了锁的过度使用可能导致串行化范围过大,影响多线程的优势,并介绍了如何避免锁带来的性能问题。文章还提到了性能方面的度量指标,包括吞吐量、延迟和并发量。总结指出并发编程是一个复杂的技术领域,需要重点关注安全性、活跃性和性能。文章以深入浅出的方式介绍了并发编程中的关键问题,为读者提供了全面的并发编程概览。
仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Java 并发编程实战》,新⼈⾸单¥59
《Java 并发编程实战》,新⼈⾸单¥59
立即购买
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
登录 后留言
全部留言(165)
- 最新
- 精选
- 峰vector是线程安全,指的是它方法单独执行的时候没有并发正确性问题,并不代表把它的操作组合在一起问木有,而这个程序显然有老师讲的竞态条件问题。
作者回复: 👍
2019-03-145197 - kaixiao7老师,串行百分比一般怎么得出来呢(依据是什么)?
作者回复: 你可以这么理解:临界区都是串行的,非临界区都是并行的,用单线程执行临界区的时间/用单线程执行(临界区+非临界区)的时间就是串行百分比
2019-03-293139 - 飘呀飘的小叶子Vector实现线程安全是通过给主要的写方法加了synchronized,类似contains这样的读方法并没有synchronized,该题的问题就出在不是线程安全的contains方法,两个线程如果同时执行到if(!v.contains(o)) 是可以都通过的,这时就会执行两次add方法,重复添加。也就是老师说的竞态条件。
作者回复: 👍
2019-03-14686 - 虎虎❤️老师讲的太好了。我没有并发的编程经验,但是可以看懂每一篇文章,也可以正确回答每节课后的习题。我觉得这次跟对了人,觉得很有希望跟着老师学好并发。 但是,这样跟着学完课程就能学好并发编程吗?老师可以给些建议吗?除了跟着课程,我还需要做些什么来巩固战果?老师能不能给加餐一篇学习方法,谢谢! 本节课总结: 安全性: 数据竞争: 多个线程同时访问一个数据,并且至少有一个线程会写这个数据。 竞态条件: 程序的执行结果依赖程序执行的顺序。 也可以按照以下的方式理解竞态条件: 程序的执行依赖于某个状态变量,在判断满足条件的时候执行,但是在执行时其他变量同时修改了状态变量。 if (状态变量 满足 执行条件) { 执行操作 } 问题: 数据竞争一定会导致程序存在竞态条件吗?有没有什么相关性? 活跃性: 死锁:破坏造成死锁的条件,1,使用等待-通知机制的Allocator; 2主动释放占有的资源;3,按顺序获取资源。 活锁:虽然没有发生阻塞,但仍会存在执行不下去的情况。我感觉像进入了某种怪圈。解决办法,等待随机的时间,例如Raft算法中重新选举leader。 饥饿:我想到了没有引入时间片概念时,cpu处理作业。如果遇到长作业,会导致短作业饥饿。如果优先处理短作业,则会饿死长作业。长作业就可以类比持有锁的时间过长,而时间片可以让cpu资源公平地分配给各个作业。当然,如果有无穷多的cpu,就可以让每个作业得以执行,就不存在饥饿了。 性能: 核心就是在保证安全性和活跃性的前提下,根据实际情况,尽量降低锁的粒度。即尽量减少持有锁的时间。JDK的并发包里,有很多特定场景针对并发性能的设计。还有很多无锁化的设计,例如MVCC,TLS,COW等,可以根据不同的场景选用不同的数据结构或设计。 最后,在程序设计时,要从宏观出发,也就是关注安全性,活跃性和性能。遇到问题的时候,可以从微观去分析,让看似诡异的bug无所遁形。
作者回复: 能看懂说明基本功很扎实啊。你的建议我会考虑的。
2019-03-14275 - 寒铁add10K() 如果用synchronized修饰 应该就没有问题了吧? get和set是synchronized不能保证调用get和set之间的没有其他线程进入get和set,所以这是导致出错的根本原因。
作者回复: 👍
2019-04-0336 - Nevermore编写并发程序的初衷是为了提升性能,但在追求性能的同时由于多线程操作共享资源而出现了安全性问题,所以才用到了锁技术,一旦用到了锁技术就会出现了死锁,活锁等活跃性问题,而且不恰当的使用锁,导致了串行百分比的增加,由此又产生了性能问题,所以这就是并发程序与锁的因果关系。
作者回复: 👍
2019-03-14333 - 亮亮void addIfNotExist(Vector v, Object o){ synchronized(v) { if(!v.contains(o)) { v.add(o); } } } 这样不知道对不对
作者回复: 对的
2019-03-14425 - 易水南风add10k的例子不明白,因为两个方法都已经加上锁了,同一个test对象应该不可能两个线程同时执行吧?
作者回复: 同时执行,指的是同时被调用。被锁串行后,还是有问题
2019-03-151123 - hanmshashouConcurrentHashMap 1.8后没有分段锁 syn + cas
作者回复: 是这样,高手!
2019-03-14216 - 探索无止境吞吐量和并发量从文中描述的概念上来看,总觉得很像,具体该怎么区分?期待指点!
作者回复: 对于一台webserver,吞吐量一般指的是server每秒钟能处理多少请求;并发量指的是有多少个客户端同时访问。
2019-03-14316
收起评论