使用内核线程实现
使用用户线程实现
广义上来讲,一个线程只要不是内核线程,那就可以认为是用户线程(User Thread,UT),而狭义的用户线程指的是完全建立在用户空间的线程库上,系统内核不能感知到线程存在的实现,用户线程的建立、同步、销毁和调度完全在用户态中完成,不需要内核的帮助。如果程序实现得当,这种线程不需要切换到内核态,因此操作可以是非常快速且低消耗的,也可以支持规模更大的线程数量,部分高性能数据库中的多线程就是由用户线程实现的。这种进程与用户线程之间1:N的关系称为一对多的线程模型。
混合实现
混合环境下,既存在用户线程,又存在轻量级进程。用户线程还是完全建立在用户空间中,而操作系统所支持的轻量级进程则作为用户线程和内核线程之间的桥梁。这种混合模式下,用户线程与轻量级进程的数量比是不定的,是M:N的关系。许多Unix系列的系统,都提供了M:N的线程模型实现。
2、java线程与操作系统之间的调度关系
到了后来,操作系统内核纷纷都支持了多线程(windows开始就支持),那么java也要考虑推卸一些责任了,这样java线程就和操作系统线程一一对应(操作系统调度,使用内核线程实现)或多多对应了(操作系统和JVM一起调度,混合实现),这个时候,如果是一一对应,那么线程的调度完全交给了操作系统内核。Linux从内核2.6开始使用NPTL (Native POSIX Thread Library)支持,但这时线程本质上还轻量级进程。 Java里的线程是由JVM来管理的,它如何对应到操作系统的线程是由JVM的实现来确定的。Linux 2.6上的HotSpot使用了NPTL机制,JVM线程跟内核轻量级进程(LWP)有一一对应的关系。线程的调度完全交给了操作系统内核,当然jvm还保留一些策略足以影响到其内部的线程调度,举个例子,在linux下,只要一个Thread.run就会调用一个fork产生一个线程。ava线程在Windows及Linux平台上的实现方式,现在看来,是内核线程的实现方式。这种方式实现的线程,是直接由操作系统内核支持的——由内核完成线程切换,内核通过操纵调度器(Thread Scheduler)实现线程调度,并将线程任务反映到各个处理器上。内核线程是内核的一个分身。程序一般不直接使用该内核线程,而是使用其高级接口,即轻量级进程(LWP),也即线程。其调度关系如下图所示
3、Java 进程与操作系统进程
在 JDK 的代码中,只提供了 ProcessImpl 类来实现 Process 抽象类。其中引用了 native 的 create, close, waitfor, destory 和 exitValue 方法。在 Java 中,native 方法是依赖于操作系统平台的本地方法,它的实现是用 C/C++ 等类似的底层语言实现。我们可以在 JVM 的源代码中找到对应的本地方法,然后对其进行分析。JVM 对进程的实现相对比较简单,以 Windows 下的 JVM 为例。在 JVM 中,将 Java 中调用方法时的传入的参数传递给操作系统对应的方法来实现相应的功能。对应关系如下图:
每个 JVM 中创建的进程都对应了操作系统中的一个进程。但是,Java 为了给用户更好的更方便的使用,向用户屏蔽了一些与平台相关的信息,这为用户需要使用的时候,带来了些许不便。
在使用 C/C++ 创建系统进程的时候,是可以获得进程的 PID 值的,可以直接通过该 PID 去操作相应进程。但是在 JAVA 中,用户只能通过实例的引用去进行操作,当该引用丢失或者无法取得的时候,就无法了解任何该进程的信息。
当然,Java 进程在使用的时候还有些要注意的事情:
- Java 提供的输入输出的管道容量是十分有限的,如果不及时读取会导致进程挂起甚至引起死锁。
- 当创建进程去执行 Windows 下的系统命令时,如:dir、copy 等。需要运行 windows 的命令解释器,command.exe/cmd.exe,这依赖于 windows 的版本,这样才可以运行系统的命令。
- 对于 Shell 中的管道 ‘ | ’命令,各平台下的重定向命令符 ‘ > ’,都无法通过命令参数直接传入进行实现,而需要在 Java 代码中做一些处理,如定义新的流来存储标准输出,等等问题。
总之,Java 中对操作系统的进程进行了封装,屏蔽了操作系统进程相关的信息。同时,在使用 Java 提供创建进程运行本地命令的时候,需要小心使用。
一般而言,使用进程是为了执行某项任务,而现代操作系统对于执行任务的计算资源的配置调度一般是以线程为对象(早期的类 Unix 系统因为不支持线程,所以进程也是调度单位,但那是比较轻量级的进程,在此不做深入讨论)。创建一个进程,操作系统实际上还是会为此创建相应的线程以运行一系列指令。特别地,当一个任务比较庞大复杂,可能需要创建多个线程以实现逻辑上并发执行的时候,线程的作用更为明显。因而我们有必要深入了解 Java 中的线程,以避免可能出现的问题。
从概念上来说,一个 Java 线程的创建根本上就对应了一个本地线程(native thread)的创建,两者是一一对应的。 问题是,本地线程执行的应该是本地代码,而 Java 线程提供的线程函数是 Java 方法,编译出的是 Java 字节码,所以可以想象的是, Java 线程其实提供了一个统一的线程函数,该线程函数通过 Java 虚拟机调用 Java 线程方法 , 这是通过 Java 本地方法调用来实现的。
以下是 Thread#start 方法的示例:
public synchronized void start() { … start0(); … }
可以看到它实际上调用了本地方法 start0, 该方法的声明如下:
private native void start0();
Thread 类有个 registerNatives 本地方法,该方法主要的作用就是注册一些本地方法供 Thread 类使用,如 start0(),stop0() 等等,可以说,所有操作本地线程的本地方法都是由它注册的 . 这个方法放在一个 static 语句块中,这就表明,当该类被加载到 JVM 中的时候,它就会被调用,进而注册相应的本地方法。
private static native void registerNatives(); static{ registerNatives(); }
本地方法 registerNatives 是定义在 Thread.c 文件中的。Thread.c 是个很小的文件,定义了各个操作系统平台都要用到的关于线程的公用数据和操作,如代码清单 2 所示。
清单 2
JNIEXPORT void JNICALL Java_Java_lang_Thread_registerNatives (JNIEnv *env, jclass cls){ (*env)->RegisterNatives(env, cls, methods, ARRAY_LENGTH(methods)); } static JNINativeMethod methods[] = { {"start0", "()V",(void *)&JVM_StartThread}, {"stop0", "(" OBJ ")V", (void *)&JVM_StopThread}, {"isAlive","()Z",(void *)&JVM_IsThreadAlive}, {"suspend0","()V",(void *)&JVM_SuspendThread}, {"resume0","()V",(void *)&JVM_ResumeThread}, {"setPriority0","(I)V",(void *)&JVM_SetThreadPriority}, {"yield", "()V",(void *)&JVM_Yield}, {"sleep","(J)V",(void *)&JVM_Sleep}, {"currentThread","()" THD,(void *)&JVM_CurrentThread}, {"countStackFrames","()I",(void *)&JVM_CountStackFrames}, {"interrupt0","()V",(void *)&JVM_Interrupt}, {"isInterrupted","(Z)Z",(void *)&JVM_IsInterrupted}, {"holdsLock","(" OBJ ")Z",(void *)&JVM_HoldsLock}, {"getThreads","()[" THD,(void *)&JVM_GetAllThreads}, {"dumpThreads","([" THD ")[[" STE, (void *)&JVM_DumpThreads}, };
到此,可以容易的看出 Java 线程调用 start 的方法,实际上会调用到 JVM_StartThread 方法,那这个方法又是怎样的逻辑呢。实际上,我们需要的是(或者说 Java 表现行为)该方法最终要调用 Java 线程的 run 方法,事实的确如此。 在 jvm.cpp 中,有如下代码段:
JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread)) … native_thread = new JavaThread(&thread_entry, sz); …
这里JVM_ENTRY是一个宏,用来定义JVM_StartThread 函数,可以看到函数内创建了真正的平台相关的本地线程,其线程函数是 thread_entry,如清单 3 所示。
清单 3
static void thread_entry(JavaThread* thread, TRAPS) { HandleMark hm(THREAD); Handle obj(THREAD, thread->threadObj()); JavaValue result(T_VOID); JavaCalls::call_virtual(&result,obj, KlassHandle(THREAD,SystemDictionary::Thread_klass()), vmSymbolHandles::run_method_name(), vmSymbolHandles::void_method_signature(),THREAD); }
可以看到调用了 vmSymbolHandles::run_method_name 方法,这是在 vmSymbols.hpp 用宏定义的:
class vmSymbolHandles: AllStatic { … template(run_method_name,"run") … }
至于 run_method_name 是如何声明定义的,因为涉及到很繁琐的代码细节,本文不做赘述。感兴趣的读者可以自行查看 JVM 的源代码。
相关推荐
JAVA线程与进程的区别JAVA线程与进程的区别
java多线程与进程调度,针对多线程和进程的讲解
进程与线程,本质意义上说, 是操作...同时由于 Java 的封装也隐藏了底层的一些概念和可操作性,本文还对 Java 进程线程和本地进程线程做了一些简单的比较,列出了使用 Java 进程、线程的一些限制和需要注意的问题。
java 查看JVM中所有的线程的活动状况 java 查看JVM中所有的线程的活动状况
一、操作系统中线程和进程的概念 2 二、Java中的线程 3 三、Java中关于线程的名词解释 3 四、线程的状态转换和生命周期 4 Java线程:创建与启动 7 Java线程:线程名称的设定及获取 10 Java线程:线程栈模型与线程的...
《JAVA多线程设计模式》PDF 下载 《Java线程 高清晰中文第二版》中文第二版(PDF) 前言 第一章 线程简介 Java术语 线程概述 为什么要使用线程? 总结 第二章 Java线程API 通过Thread类创建线程 使用Runable接口...
进程与线程的一个简单解释,教你简单区分程序设计过程中的进程与线程问题,有助于你对多线程编程的理解
现在的操作系统是多任务操作...线程总是属于某个进程,进程中的多个线程共享进程的内存。 “同时”执行是人的感觉,在线程之间实际上轮换执行。 本文档提供Java多线程编程经验,方便广大Java爱好者研究学习Java多线程
Java 进程与线程 线程的概念模型 线程类 线程的同步与互斥
每运行一个java程序会产生一个java进程,每个java进程可能包含一个或者多个线程,每一个Java进程对应唯一一个JVM实例,每一个JVM实例唯一对应一个堆,每一个线程有一个自己私有的栈。进程所创建的所有类的实例(也...
JAVA从入门到精通.孙鑫老师主讲
Java雷电游戏,主要用SWING和Java的进程,纯Java代码写的,主要为开发人员对Java线程的认识
Java并发编程进程和线程之由来Java开发Java经验技巧共3页.pdf.zip
操作系统中的进程、线程与Java的多线程
系统在运行过程中应能显示或打印各进程的状态及有关参数的变化情况; 系统设计 针对需求1、2: 为实现多个进程并发运行需要建立管理对象和进程对象的联系以便对运行的进程进行管理,故可以在管理对象中设置就绪、...
进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。比如在Windows系统中,一个运行的exe就是一个进程。 线程是指进程中的一个执行流程,一个进程中可以运行多个...
java线程进程锁资源111111
一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。 多线程是多任务的一种特别的形式,但多线程使用了更小的资源开销。 这里定义和线程相关的另一个术语 - 进程...
java实现守护进程,有单独的监听进程, 两个或多个进程,两个或多个jvm java -jar heshenboot.jar start|stop
Unix操作系统环境下,应用程序可以利用fork函数创建子进程,但子进程与该应用程序进程拥有独立的地址空间、系统资源和代码执行单元,并且进程的 调度是由操作系统来完成的,使得在应用进程之间进行通信和线程协调...