深入理解JAVA虚拟机阅读随笔(三)Java与线程
java工程师对线程一般都不难理解,我们知道线程是进程中的一条执行工作,知道多线程与单线程的区别,但很少有人知道线程的实现原理是什么,java虚拟机是怎样在服务器上创建并执行一条线程的,这就是本篇文章所要讲解的内容。
-
什么是线程
线程是比进程更轻量级的调度执行单位,线程的引入,可以把一个进程的资源分配和执行调度分开,各个线程既可以共享进程的资源(内存地址、文件I/O等),又可以独立调度(线程是CPU调度的基本单位)。
-
线程的实现方式
线程的实现方式有三种:使用内核线程实现、使用用户线程库实现、使用用户线程加轻量级进程混合实现。
1.使用内核线程实现
内核线程(Kernel-Level Thread,KLT)是直接由操作系统内核(Kernel,下面简称内核)支持的,这种线程由内核来完成线程的切换,内核通过操纵调度器(Scheduler)对线程进行调度,并负责将线程映射到各个处理器上。每个内核线程可以视为内核的一个分身,这样一个内核就可以处理多个线程,支持多线程的内核就叫做多线程内核(Multi-Threads Kernel)。
程序一般不会直接使用内核线程,而是通过内核线程的一个高级接口-轻量级进程(Light Weight Process,LWP)使用,轻量级进程就是我们通常意义上讲的线程,由于每个轻量级进程都由一个内核线程支持,因此只有先支持内核线程,才能支持轻量级进程。这种轻量级进程和内核线程之间1:1的关系称为一对一的内核模型。如果12-3所示:
由于内核线程的支持,每一个轻量级进程都是一个独立的调度单位,各个轻量级进程之间互不影响。由于是直接操作单个内核线程,轻量级进程的线程操作,析构以及同步,都需要系统调用,而系统调用的代价相对较高,需要在用户态(User Mode)和内核态(Kernel Mode)之间来回切换。其次,由于一条轻量级进程对应一条内核线程,因此,一个系统支持轻量级进程的数量是有限的。
-
使用用户线程实现
广义上讲,一个县城如果不是内核线程,就都是用户线程(User Thread,UT)。从这个角度来说,轻量级进程也是用户线程,但轻量级进程始终是由内核线程实现的,效率会受到限制;狭义上讲,用户线程是完全建立在用户空间的线程库上,系统内核完全感受不到线程存在的实现。如果程序实现得当,这种线程不需要切换到内核态,且效率更高,资源消耗更小,能支持的并发数更高,部分高性能的数据库中的多线程就是通过用户线程来实现的,这种进程与用户线程之间1:N的关系称为一对多的线程模型。如图:
使用用户线程的优势在于不需要使用内核线程,但相对的,由于所有的线程操作都需要用户程序来处理,实现起来非常复杂。线程的创建,切换和调度都需要考虑,而且由于操作系统只把处理器资源分配到进程,那诸如“阻塞如何处理”,“多处理器系统中如何将线程映射到其他处理器中”这类问题解决起来将会异常困难,甚至不能完成。因而除了在不支持多线程的操作系统(如DOS)的多线程程序和少数特殊的程序外,现在使用用户线程实现多线程的程序越来越少了,Java、Ruby都曾经在使用过用户线程后最终又放弃了它。
-
使用用户线程加轻量级进程混合实现
通过在用户线程中调用轻量级进程来调度内核线程,弥补了用户线程的缺陷。这种是多对多的线程模型。如图:
-
Java线程的实现
在JDK1.2之前,线程是基于“绿色线程”(Green Threads)的用户线程实现的,1.2之后线程模型替换为基于操作系统原生线程模型实现。因此,在目前的版本中,很大程度上决定于java虚拟机的线程是怎样映射的,虚拟机规范中也未规定这一部分要通过那种方式实现。Sun JDK的windows版和linux版都是使用一对一的线程模型实习的。而在Solaris平台中,由于操作系统同时支持一对一和多对多的线程模型,因此,Solaris的JDK提供了-XX:+UseLPWSynchronization(默认)和-XX:+UseBoundThreads两个参数来决定。
-
Java的线程调度
线程调度是指系统为线程分配处理器使用权的过程,主要调度方式有两种:协同式线程调度(Cooperative Threads-Scheduling)和抢占式线程调度(Preemptive Threads-Scheduling)。
协同式线程调度是一条线程在执行完毕以后,主动通知系统切换到另一条线程上,实现简单,不存在线程同步问题。但是有一个弊端,当由于某些原因导致单条线程阻塞,那有可能导致系统崩溃;抢占式线程调度是每个线程的执行时间由系统来控制,这样线程之间不会相互影响进度。java就是通过这种方式进程线程调度的。
java可以通过设置线程优先级来决定哪些线程可以优先获取CPU使用权,但是这样的设置也不是一定有效的,取决于java中的优先级与系统的优先级的匹配度。Java中一共设置了十个优先级,如果操作系统的优先级比java的优先级少,那一定存在java的某些优先级是在系统中处于一个级别。
-
状态转换
Java语言定义了五种线程状态,在任意一个时间点,一个线程只能有且只有一种状态。这五种状态分别是:
1.新建(NEW) 创建后尚未启动的线程
2.运行(Runable)这个状态包括了线程的Running(正在执行)和Ready(等待CPU)。
3.无限期等待(Waiting)不分配CPU执行时间,等待其他线程显示的唤醒。以下方法会让线程进入Waiting状态:
没有设置Timeout的Object.wait()。
没有设置Timeout的Thread.join()。
LockSupport.park()。
4.限期等待(Timed Waiting)同上,但是在一定时间后会由系统唤醒。以下方法会让线程进入Waiting状态:
Thread.sleep()。
设置Timeout的Object.wait()。
设置Timeout的Thread.join()。
LockSupport.parkNanos()。
LockSupport.parkUntil()。
5.阻塞(Blocked)线程等待进入同步区域的时候。
6.结束(Terminated)执行结束。