前言
想要充分发挥多核处理器系统的强大的计算能力,最简单的方式就是使用多线程。随着处理器数量的持续增长,如何高效的使用并发正变得越来越重要。这个系列文章重点学习 Java 并发相关的知识:并发可能产生的问题,怎么解决这些问题,以及一些并发工具的使用及原理。
并发简史
早期的计算机并不包含操作系统,它们从头到尾只执行一个程序,并且这个程序能访问计算机中的所有资源。这对于昂贵且稀有的计算机资源来说是一种浪费。 操作系统的出现使的计算机每次能运行多个程序,并且不同的程序能在不同的进程中运行。 之所以在计算机中加入操作系统来实现多个程序的同时执行,主要有以下原因:
资源利用率
在某些情况下,程序必须等待某个外部操作执行完成,例如输入操作或输出操作,而等待的时候其他程序无法执行。如果在等待的同时可以执行另一个程序,那么无疑将提高资源利用率。
公平性
不同的程序对计算机上的资源有共同的使用权,一种高效的运行方式是通过时间分片使这些程序能共享计算机资源。
便利性
在计算多个任务时,应该编写多个程序,每个程序执行一个任务,并在必要时进行通信。这比一个程序实现所有任务更容易实现。
这些促使进程出现的因素(资源利用率、公平性、便利性)同样也促使线程的出现。线程会共享进程范围内的资源,但每个线程有独自的程序计数器、栈以及局部变量等。
线程的优势
如果使用的当,线程可以有效的降低程序的开发和维护成本,同时提升复杂应用的性能。此外线程还可以降低代码的复杂度,使代码更容易编写、阅读和维护。
发挥多核处理器的能力
多核处理器正日益普及,并且价格也在不断降低。由于基本的调度单位是线程,如果在程序中只有一个线程,最多同时只能在一个处理器上运行,多核的优势将无法发挥,多余的计算能力也被浪费了。 使用多个线程还有助于在单核处理器系统上获得更高的吞吐率。
建模的简单性
如果在程序中只包含一种类型的任务,那么将比包含多种类型的不同任务的程序更易于编写。如果为程序中每种不同的任务都分配一个专门的线程,可以将复杂并异步的工作流分解为一组简单并同步的工作流。
线程带来的风险
Java 对线程的支持其实是一把双刃剑,Java 提供的工具简化了并发应用程序的开发,但同时也提高了对开发人员的要求,因为在更多的程序中会使用线程。
安全性问题
线程的安全性可能是非常复杂的,在没有充分同步的情况下,多个线程的执行顺序是不可预测的,甚至会产生非常奇怪的后果。 如果没有同步,无论是在编译器、硬件还是在运行时都可以随时安排操作的执行顺序。虽然这些技术有助于实现更优的性能,但它们同时也为开发人员带来了负担,因为这些优化措施可能会有线程安全问题。
活跃性问题
在开发并发代码时,一定要注意线程的安全性是不可破坏的。安全性对于单线程和多线程程序同样重要。多线程同样会导致一些单线程中不会出现的问题,如活跃性问题。 当某个操作无法继续执行下去时,就会发生活跃性问题。在串行程序中,活跃性问题的形式之一就是无意中造成的无限循环。
性能问题
活跃性问题意味着某件正确的事情总会发生,但是不够好,我们希望正确的事情尽快发生。性能问题包括多个方面,如:服务时间过长、响应不灵敏、吞吐率过低、资源消耗过高等。多线程程序中还可能存在着由于线程引入的其他问题。
当线程共享数据时,必须使用同步机制,而这些机制往往会抑制某些编译器优化。多线程频繁的进行上下文切换也会带来极大的开销。
线程无处不在
即使在程序中没有显示的创建线程,但在框架中仍可能会创建线程,因此在这些线程中调用的代码必须是线程安全的。这将给开发人员在设计和实现功能上带来沉重的负担,因为开发线程安全的类比开发非线程安全的类要更佳谨慎和细致。