搜索
写经验 领红包
 > 财经

java线程基础知识(Java线程基础)

导语:Java线程基础

本文首先介绍了以一个通用的应用程序角度来看实现线程的三种方式,之后介绍了Java线程的实现方式、线程的调度、Java线程的状态及切换相关的基础知识。对这些基础知识的了解有助于我们理解Java多线程。

实现线程的三种方式

站在操作系统的角度来看,实现线程主要有三种方式:基于内核线程实现(1:1实现),基于用户线程实现(1:N实现),基于用户线程和轻量级进程混合实现(N:M实现)。

我们先了解下相关的几个概念。

内核线程:Kernel Level Thread,简称KLT。是由操作系统内核支持的线程,它的建立和销毁都是由操作系统负责、通过系统调用完成,操作系统内核维护进程及线程的上下文信息以及线程的调度,并负责将线程的任务映射到各个处理器上。

轻量级进程:Light-Weight Process,简称LWP。是一种由内核支持的用户线程,每一个轻量级进程都与一个都与一个特定的内核线程关联,它是基于内核线程的高级抽象。操作系统只有先支持内核线程,才能有轻量级进程。每一个进程有一个或多个LWP,每个LWP由一个内核线程支持。为什么说是轻量级呢?在于它只有一个最小的执行上下文和调度程序所需的统计信息,它只带有进程执行相关的信息,与父进程共享进程地址空间。

Linux并没有为线程准备特定的数据结构,因为Linux只有task_struct这一种描述进程的结构体。在内核看来只有进程而没有线程,线程调度时也是当做进程来调度。Linux所谓的线程其实就是与其他进程共享资源的轻量级进程。

用户线程:User Thread,简称UT。广义上来讲,一个线程只要不是内核线程,都可以认为是用户线程,从这个角度看,轻量级进程也属于用户线程,但轻量级进程的实现始终是建立在内核之上的,许多操作都要进行系统调用。而狭义上的用户线程指的是完全建立在用户空间的线程库,系统内核不能感知到用户线程的存在及如何实现。用户线程的创建、调度、同步和销毁完全在用户态中完成,由用户空间的库函数完成,不需要内核的参与。

了解了这些概念,我们再来看实现线程的三种方式。

内核线程实现

基于内核线程的实现方式,应用程序一般不会直接使用内核线程,而是使用内核线程的一种抽象接口——轻量级进程(LWP),由于LWP与内核线程之间是1:1的关系,因此这种实现方式称为一对一的线程模型。如下图:

由于内核线程的支持,每个LWP都是一个独立的调度单元,即使其中某一个LWP在系统调用中被阻塞了,也不会影响整个进程继续工作。LWP也有局限性,首先,由于是基于内核线程实现的,所以线程的创建、调度等各种操作都需要进行系统调用,而系统调用的代价相对较高,需要在用户空间和内核空间来回切换。其次,LWP要消耗一定的内核资源(如内核线程的栈空间),因此系统支持的LWP数量是有限的。

用户线程实现

因为LWP的局限性,并不具备通常意义上的用户线程的优点。这里所说的用户线程实现,是指狭义上的用户线程,完全建立在用户空间的线程库上,通常不需要切换到内核空间,因此线程的各种操作可以是非常快速且低消耗的,也能够支持规模更大的线程数量。这种进程与用户线程之间的关系是1:N,称为一对多的线程模型。如下图:

用户线程实现方式的优点是不需要内核参与,缺点是所有的线程操作都需要用户程序自己去处理,线程的创建、调度、销毁等都是用户程序必须考虑的问题,如果触发了引起阻塞的系统调用,会阻塞该线程所属的整个进程。此外,系统只调度进程,感知不到用户线程,用户线程只能参与竞争该进程的处理器资源,不能参与全局处理器资源的竞争,也就不能发挥多核CPU的优势。

混合实现

混合实现方式是指将内核线程与用户线程一起使用的实现方式,既存在用户线程,也存在轻量级进程。用户线程还是完全建立在用户空间,线程的创建、调度等依然是低消耗的,并且可以支持大规模的用户线程。而操作系统内核支持的LWP则作为用户线程和内核线程之间的桥梁,这样可以使用内核提供的线程调度功能及处理器映射,大大降低了整个进程被阻塞的风险。这种混合模式中,用户线程与轻量级进程的数量比是不定的,称为多对多的的线程模型。

Java线程的实现

Java线程在JDK1.2以前,是基于一种被称为“绿色线程”(Green Threads)的用户线程实现的,但从JDK1.3起,主流平台上的主流商用Java虚拟机的线程模型普遍都被替换为基于操作系统原生线程实现,即采用1:1的线程模型。

以HotSpot虚拟机为例,它的每一个Java线程都是直接映射到一个操作系统原生线程来实现的,而且中间没有额外的间接结构,所以HotSpot自己并不会去调度线程,只是可以设置线程优先级给操作系统提供调度建议。所以何时冻结或唤醒线程、该给线程分配多少处理器执行时间、把线程映射到哪个处理器核心去执行等,都是由操作系统完成。

操作系统支持怎样的线程模型,在很大程度上会影响上面的Java虚拟机的线程是怎样映射的,这一点在不同的平台上很难达成一致。因此,Java虚拟机规范中并没有限定Java线程使用哪种线程模型实现。线程模型只是对线程的并发规模和操作成本产生影响,对Java应用程序来说是完全透明的。

线程的调度

线程调度是指系统为线程分配处理器使用权的过程,调度方式主要有两种:协同式线程调度和抢占式线程调度。

协同式调度:线程的执行时间由线程本身来控制,线程把自己的工作执行完了后,要主动通知系统切换到其他线程。这种调度方式实现简单,切换操作对于线程自身是可知的,一般不存在线程同步的问题。它的缺点是:线程执行时间不可控制,如果某个线程存在问题,一直不告知系统切换线程,程序就会造成阻塞。

抢占式调度:每个线程的执行时间由系统分配,线程的切换不由线程自身决定。在这种调度方式下,线程的执行时间是系统可控的,不会因为一个线程导致整个进程阻塞。

Java使用的线程调度方式就是抢占式调度。虽说Java线程调度是系统自动完成的,但是我们可以通过设置线程的优先级来“建议”操作系统给高优先级的线程多分配一点执行时间,低优先级的线程则少分配一点。Java语言一共设置了10个级别的线程优先级,当两个线程同时处于Ready状态时,优先级越高的线程越容易被系统选择执行。

不过,设置线程优先级并不是一项稳定的调控手段,是因为线程调度最终还是由操作系统说了算,操作系统线程优先级的概念并不一定与Java线程优先级一一对应,例如Windows系统中就只有7种优先级,所以会出现多个Java线程优先级对应同一个操作系统优先级的情况。Windows平台的虚拟机中使用了除THREAD_PRIORITY_IDLE之外的其余6中线程优先级,因此在Windows系统下设置线程优先级1和2,3和4,6和7,8和9的效果是完全相同的。Java线程优先级与Windows线程优先级对应关系如下:

Java线程的状态

Java语言定义了6种线程状态,分别是:

新建(New):创建后尚未启动的线程处于这种状态。运行(Runnable):包括操作系统线程状态对应的Running和Ready,处于此状态的线程有可能正在被执行,也有可能正在等待操作系统为它分配执行时间。无限期等待(Waiting):处于这种状态的线程不会被分配执行时间,需要等待被其他线程显式唤醒。限期等待(Timed Waiting):处于这种状态的线程不会被分配执行时间,无需等待被其他线程显式唤醒。在限定时间之后它们会由系统唤醒。阻塞(Blocked):线程等待获取一个排它锁,即线程在等待进入synchronized关键字修饰的方法或代码块时的状态。结束(Terminated):线程执行结束的状态,即当线程的run()方法执行完成时,或主线程的main()方法执行完成时的状态。

在任意一个时间点,一个线程只能处于其中的一种状态,并且可以通过特定的条件在不同状态之间转换。如下图:

对操作系统而言,New和Terminated状态是没有太大意义的。通常只关注线程的三种状态:

Ready:线程等待分配执行时间。Running:线程正在执行。Waiting:线程未分配到执行时间,处于等待或阻塞。

这一点在概念上与Java线程定义的状态有所区别,二者之间的关系如下图所示:

对于操作系统来说,Blocked、Waiting、Timed Waiting这几个状态,线程都没有分配到执行时间。叫它挂起、阻塞、等待都可以,很多文章中也并未严谨区分这几个词。看下与此关联的一个问题:

Java线程调用IO阻塞方法,线程处于什么状态?

答案是:Runnable状态。本质上来说,此时线程不会分配到执行时间,因为它在等待IO操作完成,然后变为Ready状态。但是在Java看来,等待IO操作与等待CPU分配执行时间是一样的,都属于Runnable状态。

本文内容由快快网络小面创作整理编辑!