07 | 安全性、活跃性以及性能问题
安全性问题
线程是否安全?本质上就是正确性,而正确性的含义就是程序按照我们期望的执行
存在共享数据并且该数据会发生变化,通俗地讲就是有多个线程会同时读写同一数据
- 数据竞争:当多个线程同时访问同一数据,并且至少有一个线程会写这个数据的时候,如果我们不采取防护措施,那么就会导致并发 Bug。
当产生数据竞争时,不仅仅加锁就能解决这个问题的,要合理的利用锁的范围能力,例如:方法被同时调用,不是先拿set的锁,是先拿get的锁。先计算参数,后调用方法
- 竞态条件:指的是程序的执行结果依赖线程执行的顺序
面对数据竞争和竞态条件问题:用互斥这个技术方案,而实现互斥的方案有很多,CPU 提供了相关的互斥指令,操作系统、编程语言也会提供相关的 API。从逻辑上来看,我们可以统一归为:锁。
活跃性问题
除了死锁外,还有两种情况,分别是“活锁”和“饥饿”
活锁:有时线程虽然没有发生阻塞,但仍然会存在执行不下去的情况
例如:路人甲从左手边出门,路人乙从右手边进门,两人为了不相撞,互相谦让,路人甲让路走右手边,路人乙也让路走左手边,结果是两人又相撞了。
饥饿:指的是线程因无法访问所需资源而无法执行下去的情况
解决饥饿:一是保证资源充足,二是公平地分配资源,三就是避免持有锁的线程长时间执行
性能问题
使用“锁”要非常小心,但是如果小心过度,也可能出“性能问题”。“锁”的过度使用可能导致串行化的范围过大,这样就不能够发挥多线程的优势了,而我们之所以使用多线程搞并发程序,为的就是提升性能。
方法:
- 最好的方案自然就是使用无锁的算法和数据结构了,例如线程本地存储 (Thread Local Storage, TLS)、写入时复制 (Copy-on-write)、乐观锁等;Java 并发包里面的原子类也是一种无锁的数据结构;
- 减少锁持有的时间,例如使用细粒度的锁,一个典型的例子就是 Java 并发包里的 ConcurrentHashMap,它使用了所谓分段锁的技术(这个技术后面我们会详细介绍);还可以使用读写锁,也就是读是无锁的,只有写的时候才会互斥。
学习自:https://time.geekbang.org/column/intro/100023901?tab=catalog