并发中的问题

安全性问题

线程安全的含义就是程序按照我们期望的执行

  • 存在共享数据并且该数据会发生变化,有多个线程会同时读写同一数据。

    当多个线程同时访问同一数据,并且至少有一个线程会写这个数据的时候,如果我们不采取防护措施,那么就会导致并发 Bug,专业的术语,叫做数据竞争
    竞态条件,指的是程序的执行结果依赖线程执行的顺序。

活跃性问题

活跃性问题,指的是某个操作无法执行下去。“死锁”、“活锁”、“饥饿”。

  • 死锁

    线程会互相等待,而且会一直等待下去,在技术上的表现形式是线程永久地“阻塞”了。

    线程之间对于资源互不相让,持有对方的锁

    主动释放它占有的资源等

  • 活锁

    有时线程虽然没有发生阻塞,但仍然会存在执行不下去的情况。

    线程之间对于资源互相谦让

    “等待一个随机时间”的方案虽然很简单,却非常有效

  • 饥饿

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

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

    解决“饥饿”问题有三种方案:一是保证资源充足,二是公平地分配资源,三就是避免持有锁的线程长时间执行。这三个方案中,方案一和方案三的适用场景比较有限,因为很多场景下,资源的稀缺性是没办法解决的,持有锁的线程执行的时间也很难缩短。方案二的适用场景相对来说更多一些。
    公平地分配资源在并发编程里,主要是使用公平锁。所谓公平锁,是一种先来后到的方案,线程的等待是有顺序的,排在等待队列前面的线程会优先获得资源。

性能问题

“锁”的过度使用可能导致串行化的范围过大,这样就不能够发挥多线程的优势了,而使用多线程搞并发程序,为的就是提升性能。

  • 方案层面,解决这个问题:

    第一,既然使用锁会带来性能问题,那最好的方案自然就是使用无锁的算法和数据结构了。例如线程本地存储 (Thread Local Storage, TLS)、写入时复制 (Copy-on-write)、乐观锁等;Java 并发包里面的原子类也是一种无锁的数据结构;Disruptor 则是一个无锁的内存队列,性能都非常好


    第二,减少锁持有的时间。互斥锁本质上是将并行的程序串行化,所以要增加并行度,一定要减少持有锁的时间。例如使用细粒度的锁,一个典型的例子就是 Java 并发包里的 ConcurrentHashMap,它使用了所谓分段锁的技术;还可以使用读写锁,也就是读是无锁的,只有写的时候才会互斥。

  • 性能的度量指标

    1、吞吐量:指的是单位时间内能处理的请求数量。吞吐量越高,说明性能越好。
    2、延迟:指的是从发出请求到收到响应的时间。延迟越小,说明性能越好。
    3、并发量:指的是能同时处理的请求数量,一般来说随着并发量的增加、延迟也会增加。所以延迟这个指标,一般都会是基于并发量来说的。例如并发量是 1000 的时候,延迟是 50 毫秒。

猜你喜欢

转载自blog.csdn.net/weixin_46488959/article/details/127149681