Java与线程
线程是比进程更轻量级的调度执行单位,线程的引入,可以吧一个进程的资源分配和执行调度分开,各个线程既可以共享进程资源,又可以独立调度。目前线程时Java里面进行处理器资源调用的最基本单位。
线程的实现
实现线程只要有三种方式:使用内核线程实现(1:1实现),使用用户线程实现(1:N实现),使用用户线程加轻量级进程或者实现(N:M实现)。
内核线程实现
使用内核线程实现的方式也被称为1:1实现。内核线程就是直接由操作系统内核支持的线程,这种线程由内核来完成线程切换,内核通过操纵调度器对线程进行调度,并负责将线程的任务映射到各个处理器上。每个内核线程可以视为内核的一个分身,这样操作系统就有能力同时处理多件事情,支持多线程的内核就称为多线程内核。
程序一般不会直接使用内核线程,而是使用内核线程的一种高级接口-轻量级进程(Light Weight Process,LWP),轻量级进程就是我们通常意义上所讲的线程,由于每个轻量级进程都由一个内核线程支持,因此只有先支持内核线程,才能有轻量级进程。这种轻量级进程和内核线程之间1:1的关系成为一对一的线程模型。
由于内核线程的支持,每个轻量级进程都成为一个独立的调度单元,即使其中某一个轻量级进程在系统调用中被阻塞了,也不会影响整个进程继续工作。轻量级进程也具有它的局限性:首先,由于是基于内核线程实现的,所以各种线程操作,如创建、析构及同步,都需要进行系统调用。而系统调用的代价相对较高,需要在用户态和内核态中来回切换。其次,每个轻量级进程都需要一个内核线程的支持,因此轻量级进程要消耗一定的内核资源,因此一个系统支持轻量级进程的数量是有限的。
用户线程实现
用户线程指的是完全建立在用户空间的线程库上,系统内核不能感知到用户线程的存在以及如何实现的,用户线程的建立、同步、销毁和调用完全在用户态中完成,不需要内核的帮助。这种进程与用户线程之间的1:N的关系成为一对多的线程模型。
用户线程的优势在于不需要系统内核支援,劣势也在于没有系统内核的支援,所有的线程操作都需要用户程序自己处理。因此使用用户线程实现的程序一般都比较复杂。
混合实现
混合实现时,用户线程还是完全建立在用户空间中,而操作系统提供支持的轻量级进程则作为用户线程和内核线程之间的桥梁。在这种混合模式中,用户线程与轻量级进程的数量比是不定的,即为 N:M 的关系,这种就是多对多的线程模型。
混合实现的好处:
- 用户线程的操作依然廉价,并且可以支持大规模的用户线程并发。
- 可以使用内核提供的线程调度功能及处理器映射。
- 由于用户线程的系统调用要通过轻量级进程来完成,因此大大降低了整个进程被完全阻塞的风险。
Java线程实现
从JDK1.3起,“主流”平台上的“主流”商用Java虚拟机的线程模型普遍都被替换为基于操作系统原生线程模型来实现,即采用1:1的线程模型。
Java线程调度
线程调度是指系统为线程分配处理器使用权的过程,主要调度方式有两种:协同式线程调度和抢占式线程调度。
协同式线程调度
线程的执行时间由线程本身来控制,线程执行完之后,主动通知系统切换到另外一个线程上。
协同式线程调度最大的好处是实现简单,而且切换线程的操作对线程自己是可知的,所以没有什么线程同步的问题。它的坏处就是线程执行时间不可控,如果一个线程编写有问题,一直不告知系统进行线程切换,那么程序就会一直阻塞在那里。
抢占式线程调度
每个线程由系统来分配执行时间,线程的切换不由线程本身来决定。
使用抢占式线程调度时,线程的执行时间是系统可控的,不会有一个线程导致整个进程阻塞的问题。
Java 使用的线程调度方式就是抢占式调度。
线程状态
Java语言定义了6种线程状态,在任意一个时间点中,一个线程只能由且只有其中的一种状态,并且可以通过特定的方法在不同状态之间切换。这6状态分别是:
- 新建:创建后尚未启动的线程处于这种状态
- 运行:包括操作系统线程状态中的Running和Ready,也就是处于此状态的线程有可能正在执行,也有可能正在等待着操作系统为它分配执行时间
- 无限期等待:处于这种状态的线程不会被分配处理器执行时间,它们要等待被其他线程显式唤醒。以下方法会让线程陷入无限期的等待状态:
- 没有设置Timeout参数的Object::wait()方法
- 没有设置Timeout参数的Thread::join()方法
- LockSupport::park()方法
- 限期等待:处于这种状态的线程也不会被分配处理器执行时间,不过无需等待被其他线程显式唤醒,在一定时间之后它们会由系统自动唤醒。以下方法会让线程进入限期等待状态:
- Thread::sleep()方法
- 设置了Timeout参数的Object::wait()方法
- 设置了Timeout参数的Thread::join()方法
- LockSupport::parkNanos()方法
- LockSupport::parkUntile()方法
- 阻塞:线程被阻塞了,“阻塞状态”与“等待状态”的区别是“阻塞状态”在等待着获取一个排他锁,这个时间将在另外一个线程放弃这个锁的时候发生;而等待状态则是在等待一段时间,或者唤醒动作的发生。在程序等待进入同步区域的时候,线程将进入这种状态。
- 结束:已终止线程的线程状态,线程已经结束运行。