多线程的风险漫谈

Posted by Coderidea on September 11, 2020

线程的风险

Java对线程内置支持是一把双刃剑。它通过提供语言和类库,以及一个规范的跨平台存储模型,简化了并发应用的开发。这样做同时提高了开发人员门槛,因为更多的程序需要使用线程,主流的开发人员都必须知道线程安全性的问题。

并发危险:竞争条件(race condition)。因为线程共享相同的内存地址空间,且并发的运行,它们可能访问或修改其他线程正在使用的变量。这其中存在着巨大风险,当数据以外改变时,线程会出现混乱。给顺序编程模型引入了一些非顺序因素,这可能造成混乱,并且难以发现错误的原因。为了使多线程程序的行为可预见,访问共享变量必须经过合理的协调,这样线程才不会相互干扰。Java提供了同步机制来协调这样的访问。

活跃度的危险:活跃度失败(liveness failure)。当一个活动进入某种它永远无法继续执行的状态时,活跃度失败就发生了。包括死锁(dead lock)、饥饿(starvation)、活锁(livelock)。

性能危险: 性能问题包括服务时间、响应性、吞吐量、资源消费或者可伸缩性

的不良表现。设计良好的应用使用线程,能够获得纯粹的性能收益,但是线程会给运行时带来一定程序的开销。上下文切换(Context switches)——当调度程序临时挂起当前运行的线程时,另一个线程开始运行——这在多个线程组成的应用程序中是很频繁的,并且带来巨大的系统开销;保存和恢复线程执行的上下文,离开执行的现场,并且CPU的时间会花费在对线程的调度上。当线程共享数据的时候,它们必须使用同步机制,这个机制会限制编译器的优化,能够清空或锁定内存和高速缓存,并在共享内存总线上创建同步通信。这些因素引入了新的性能开销。

 

线程安全

编写线程安全的代码,本质上就是管理对状态(state)的访问,而且通常都是共享(一个变量可以被多个线程访问)、可变(变量的值在其生命周期内可以改)的状态。

什么是线程安全

当多个线程访问一个类时,如果不用考虑这些线程在运行时环境下的调度和交替执行,并且不需要额外的的同步及在调用方代码不必作其他的协调,这个类的行为仍然是正确的,那么称这个类是线程安全的。

无状态的对象永远是线程安全的。

原子性:假设有操作A和操作B,如果从执行A的线程角度看,当其他线程执行B时,要么B全部执行完成,要么一点都没有执行,这样A和B互为原子操作,一个原子操作是指,该操作对于所有的操作,包括它自己,都满足前面描述的状态。

锁:java 提供了强制原子性的内置锁机制:synchronized块。它包括两部分:锁对象引用,以及这个锁保护的代码块。因为锁使得线程能够串行地(serialized)访问它所保护的代码路径,所以我们可以用锁创建相关的协议,以保证线程对共享状态的独占访问。只要始终如一的遵循这些协议,就能够确保状态的一致性。

本文首发于个人微信公众号:webguan ;欢迎您的关注