并发编程带来的问题,从微观上来说,是可见性,原子性和有序性问题,从宏观角度来说,便是安全性,活跃性和性能问题。
1️⃣何为安全性
平时我们常谈到,这个方法是不是线程安全的,这个类是不是线程安全的等等。何为线程安全?便是程序执行结果的正确性,程序执行结果是我们预期的。
安全性问题,是因为多个线程同时对共享资源进行读写,由于竞态条件(指程序的执行结果依赖线程执行的顺序),导致数据不一致而产生,那么只要我们不对数据进行共享,或者对共享资源的操作,只能读不能写,和互斥,便能杜绝安全性问题。
可以通过Thread Local Storage,final和锁来解决。
2️⃣何为活跃性
活跃性关注的是,代码是否能够正常执行下去。“死锁”,“活锁”和“饥饿”。
“死锁”,线程持有互斥资源相互等待,导致均阻塞的情况;
“活锁”,线程虽没有阻塞,仍然执行不下去的情况;如线程相互等待的情况。
“饥饿”,在CPU资源紧张的情况下,优先级低的线程执行机率很小,或者由于线程持有锁时间过长,导致其他线程无法获取锁而阻塞。线程因无法访问所需资源而无法执行下去,便是”饥饿“。
活锁的解决方案
相互等待的线程,设置一个随机的等待时间,降低“碰撞”的机率。
饥饿的解决方案
解决方案:保证资源充足,公平分配资源,或避免持有锁的线程长时间执行。一、三不使用场景有限,方案二公平分配资源场景更多,并发编程里,主要使用公平锁来公平分配资源。
3️⃣性能问题
锁的过度使用导致串行化范围过大,线程持有锁的时间过长均会带来性能问题。使用多线程的目的是为了提高性能,而不是"搬石头,砸自己的脚"。
锁带来的性能问题,可以使用无锁的算法和数据结构来解决。如Thread Local Storage, copy-on-write, CAS原子类和乐观锁等。
占据锁时间过长的问题,我们可以将锁的粒度细化,如CurrentHashMap,或读写锁等。
性能问题关注三个指标,
- 吞吐量:单位时间处理的请求数量,吞吐量⬆,性能⬆
- 延迟:从发出请求到收到响应的时间,延迟⬇,性能⬆
- 并发量:能同时处理的请求数量,并发量⬆,性能⬆
一般来说,随着并发量的增加,延迟会增加。