并发编程中的安全性、活跃性以及性能问题

安全性

并发bug三大源头

源头

  • 原子性问题
  • 可见性问题
  • 有序性问题

bug风险点

存在共享数据并且该数据会发生变化(即多个线程会同时读写同一数据)

分类

  1. 数据竞争
    当多个线程同时访问同一数据,并且至少有一个线程会写这个数据。

    假设 count=0,当两个线程同时执行 get() 方法时,get() 方法会返回相同的值 0,两个线程执行 get()+1 操作,结果都是 1,之后两个线程再将结果 1 写入了内存。你本来期望的是 2,而结果却是 1。
    
  2. 竞态条件
    当多个线程或进程同时访问并试图改变同一份数据时,由于执行顺序的问题,导致程序行为变得不可预测。
    竞争条件可能导致数据的不完整、不准确或不可靠。
    在编程中,解决竞争条件的方法包括使用锁、原子操作、信号量等同步机制,以确保在任何时候只有一个线程或进程可以访问共享数据。
    存在竞态条件的例子:

    public class Test {
        private long count = 0;
        synchronized long get(){
            return count;
        }
        synchronized void set(long v){
            count = v;
        }
        void add10K() {
            int idx = 0;
            while(idx++ < 10000) {
                set(get()+1); //set和get联合使用并非原子操作,存在竞态条件
            }
        }
    }
    

    假设 count=0,如果两个线程完全同时执行,那么结果是 1;如果两个线程是前后执行,那么结果就是 2。

活跃性

指某个操作无法执行下去,存在原因:死锁、活锁、饥饿。

死锁

线程会互相等待,而且会一直等待下去。

同时满足以下条件则发生死锁:

  1. 资源互斥
  2. 持有并等待
  3. 循环等待
  4. 资源不可剥夺

针对不同条件可以考虑对应措施:

  • 持有并等待
    一次性申请所有的资源,这样就不存在等待

  • 循环等待
    资源按序申请

  • 资源不可剥夺
    占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源

活锁

线程间资源冲突激烈,引起线程不断的尝试获取资源,不断的失败。活锁有可能自己解开。

解决方案:等待一个随机时间。

饥饿

线程因无法访问所需资源而无法执行下去的情况。

在 CPU 繁忙的情况下,优先级低的线程得到执行的机会很小,就可能发生线程“饥饿”;持有锁的线程,如果执行的时间过长,也可能导致“饥饿”问题。

3种解决方案:

  1. 保证资源充足
  2. 公平分配资源
  3. 避免持有锁的线程长时间执行

方案1和3的适用场景有限,一般2多点,如公平锁。

公平锁:先来后到的方案,线程的等待是有顺序的,排在等待队列前面的线程会优先获得资源

性能问题

“锁”的过度使用可能导致串行化的范围过大,降低性能。

阿姆达尔(Amdahl)定律:处理器并行运算之后效率提升的能力
image

n 可以理解为 CPU 的核数,p 可以理解为并行百分比,那(1-p)就是串行百分比。
假设 CPU 的核数(也就是 n)无穷大,那加速比 S 的极限就是 20。

性能解决方案

  1. 使用无锁的算法和数据结构
    如线程本地存储 (Thread Local Storage, TLS)、写入时复制 (Copy-on-write)、乐观锁等;Java 并发包里面的原子类也是一种无锁的数据结构;Disruptor 则是一个无锁的内存队列

  2. 减少锁持有的时间
    互斥锁本质上是将并行的程序串行化,所以要增加并行度,一定要减少持有锁的时间。

    如使用细粒度的锁,一个典型的例子就是 Java 并发包里的 ConcurrentHashMap(jdk1.7及以下),它使用了所谓分段锁的技术;还可以使用读写锁,也就是读是无锁的,只有写的时候才会互斥。

性能指标

  1. 吞吐量:指的是单位时间内能处理的请求数量。吞吐量越高,说明性能越好。

  2. 延迟:指的是从发出请求到收到响应的时间。延迟越小,说明性能越好。

  3. 并发量:指的是能同时处理的请求数量,一般来说随着并发量的增加、延迟也会增加。所以延迟这个指标,一般都会是基于并发量来说的。例如并发量是 1000 的时候,延迟是 50 毫秒。

posted @ 2023-03-18 15:37  kiper  阅读(33)  评论(0编辑  收藏  举报