一、概念术语
进程:进程通常是程序、应用的同义词。不过,用户所看到的一个单独的应用事实上可能还会有一系列的协作进程(cooperating processes
),例如Linux上就可以通过fork创建一个进程副本。最简单的情况下,一个应用就是一个进程。大部分Java虚拟机的实现都是作为单进程运行的,但是我们也可以通过ProcessBuilder来创建附加的进程。
线程:线程有时又称之为轻量级进程
。但是创建一个线程要消耗的资源通常比创建进程少的多。一个进程内的多个线程会共享进程的资源,同时也会有自己私有的资源。线程必须存在于进程中,每个进程至少要有一个线程作为程序的入口,称之为主线程。
并发:在单核CPU的计算机上,即使操作系统支持多任务同时运行,但事实上在任一时刻,只有一个进程中的一个线程在运行,因为一个CPU同一时刻只能处理一个指令。看起来在同时执行的多个任务其实是抢占式的重叠交替执行,这就是并发。
并行:在多核CPU的计算机上,每个CPU核心可以在同一时刻同时执行不同的指令,从而能够实现并行的多线程执行模型。
二、Java与线程
一个进程内可以创建的线程个数不但受到不同操作系统使用的线程模型
限制,还要受到系统资源比如内存大小的限制,因为每个线程都有自己的栈内存空间。当然对于Java进程创建的线程,栈内存空间主要是受到JVM可以使用的内存大小的影响,而不是操作系统的总内存。
Java里的线程是由JVM来管理的,它如何对应到操作系统的线程是由JVM的实现来确定的。Linux 2.6上的HotSpot使用了NPTL机制,JVM线程跟内核线程有一一对应的关系。这样做的好处是什么?线程的调度完全交给了操作系统内核,当然jvm还保留一些策略足以影响到其内部的线程调度。
三、多线程的优点与代价
多线程可以为程序设计带来如下优势:
* 资源利用率更高
例如,通过磁盘或者网络IO进行文件/内容检索之后进行特定的处理中,由于磁盘/网络IO操作需要等待相对长的时间,而CPU在这段时间是处于空闲状态,其深层次的原因是对于IO操作(这里主要指磁盘IO),往往是通过硬件直接存取器(DMA)来执行的,也就是说,CPU只需要发送一个指令给DMA去执行对应的IO操作即可,指令发送是一瞬间的事,发送完成CPU就可以干其他的事了。如果采用多线程的方式,那么就可以使CPU在等待的过程中去处理其他可以被处理的逻辑,从而对CPU的利用率大大提高。
* 程序响应更快
在桌面应用类似的C/S程序设计中,往往用户提交的耗时任务不需要立即返回,但是用户界面不能够一直处于等待的不可用状态,用户可以在这段时间进行其它操作,当耗时任务完成之后主动通知给用户,这种多线程的设计方式为用户提供了更快更优雅的体验。
多线程不仅为程序设计带来了好处,同时又相应的需要付出一些代价:
* 程序设计更复杂
多线程的并发程序设计中如果涉及到共享数据的访问,如何防止并发访问导致共享数据的混乱将是非常困难以及使程序设计更复杂的因素。
* 多线程的上下文切换开销
当CPU从执行一个线程切换到执行另外一个线程的时候,它需要先存储当前线程的本地的数据,程序指针等,然后载入另一个线程的本地数据,程序指针 等,最后才开始执行。这种切换称为“上下文切换”(“context switch
”)。CPU会在一个上下文中执行一个线程,然后切换到另外一个上下文中执行另外一个线程。上下文切换并不廉价。如果没有必要,应该减少上下文切换的发生。
* 线程栈空间增加了内存资源消耗
每一个线程启动后,进程也要给线程分配一定的内存,让其来保存自己的私有数据。JVM划分给每个线程的内存区域称之为线程栈内存
。默认情况下,栈内存的大小1M。也就是说,你每多启动一个线程,至少要多消耗1M的内存资源。