与许多其他计算机语言不同,Java提供了对多线程的内置支持。Java中的多线程包含两个或多个可以同时运行的部分。Java线程实际上是一个轻量级进程。
本文将介绍许多人发现棘手或难以理解的Java Thread概念。
我将讨论以下主题:
在继续第一个主题之前,请考虑以下示例:
想象一下具有许多复杂功能的股票经纪人应用程序,例如
这些是耗时的功能。在单线程运行时环境中,这些操作将一个接一个地执行。仅当上一个操作完成后,才能执行下一个操作。
如果历史分析需要半小时,并且用户选择执行下载并事后检查,则警告可能来不及买卖股票。这是一种需要多线程处理的应用程序。理想情况下,下载应在后台(即在另一个线程中)进行。这样,其他进程可能会同时发生,从而例如可以立即传达警告。一直以来,用户都在与应用程序的其他部分进行交互。分析也可能在单独的线程中进行,因此用户可以在计算结果的同时使用应用程序的其余部分。
这是Java线程提供帮助的地方。
线程实际上是一个轻量级进程。与许多其他计算机语言不同,Java为多线程编程提供了内置支持。多线程程序包含可以同时运行的两个或多个部分。这种程序的每个部分都称为一个线程,每个线程都定义了一个单独的执行路径。因此,多线程是多任务的一种特殊形式。
Java运行时系统在很多方面都依赖于线程。线程通过防止浪费CPU周期来降低效率。
线程以几种状态存在:
内核线程模型
内核线程模型即完全依赖操作系统内核提供的内核线程(Kernel-Level Thread ,KLT)来实现多线程。在此模型下,线程的切换调度由系统内核完成,系统内核负责将多个线程执行的任务映射到各个CPU中去执行。
程序一般不会直接去使用内核线程,而是去使用内核线程的一种高级接口——轻量级进程(Light Weight Process,LWP),轻量级进程就是我们通常意义上所讲的线程,由于每个轻量级进程都由一个内核线程支持,因此只有先支持内核线程,才能有轻量级进程。这种轻量级进程与内核线程之间1:1的关系称为一对一的线程模型。
用户进程使用系统内核提供的接口———轻量级进程(Light Weight Process,LWP)来使用系统内核线程。在此种线程模型下,由于一个用户线程对应一个LWP,因此某个LWP在调用过程中阻塞了不会影响整个进程的执行。
由于内核线程的支持,每个轻量级进程都成为一个独立的调度单元,即使有一个轻量级进程在系统调用中阻塞了,也不会影响整个进程继续工作,但是轻量级进程具有它的局限性:
首先,由于是基于内核线程实现的,所以各种线程操作,如创建、析构及同步,都需要进行系统调用。而系统调用的代价相对较高,需要在用户态(User Mode)和内核态(Kernel Mode)中来回切换。
其次,每个轻量级进程都需要有一个内核线程的支持,因此轻量级进程要消耗一定的内核资源(如内核线程的栈空间),因此一个系统支持轻量级进程的数量是有限的。
用户线程模型
从广义上来讲,一个线程只要不是内核线程,就可以认为是用户线程(User Thread,UT),因此,从这个定义上来讲,轻量级进程也属于用户线程,但轻量级进程的实现始终是建立在内核之上的,许多操作都要进行系统调用,效率会受到限制。
而狭义上的用户线程指的是完全建立在用户空间的线程库上,系统内核不能感知线程存在的实现。用户线程的建立、同步、销毁和调度完全在用户态中完成,不需要内核的帮助。如果程序实现得当,这种线程不需要切换到内核态,因此操作可以是非常快速且低消耗的,也可以支持规模更大的线程数量,部分高性能数据库中的多线程就是由用户线程实现的。这种进程与用户线程之间1:N的关系称为一对多的线程模型。
使用用户线程的优势在于不需要系统内核支援,劣势也在于没有系统内核的支援,所有的线程操作都需要用户程序自己处理。线程的创建、切换和调度都是需要考虑的问题,而且由于操作系统只把处理器资源分配到进程,那诸如“阻塞如何处理”、“多处理器系统中如何将线程映射到其他处理器上”这类问题解决起来将会异常困难,甚至不可能完成。因而使用用户线程实现的程序一般都比较复杂,此处所讲的“复杂”与“程序自己完成线程操作”,并不限制程序中必须编写了复杂的实现用户线程的代码,使用用户线程的程序,很多都依赖特定的线程库来完成基本的线程操作,这些复杂性都封装在线程库之中,除了以前在不支持多线程的操作系统中(如DOS)的多线程程序与少数有特殊需求的程序外,现在使用用户线程的程序越来越少了,Java、Ruby等语言都曾经使用过用户线程,最终又都放弃使用它。
混合线程模型
线程除了依赖内核线程实现和完全由用户程序自己实现之外,还有一种将内核线程与用户线程一起使用的实现方式。在这种混合实现下,既存在用户线程,也存在轻量级进程。用户线程还是完全建立在用户空间中,因此用户线程的创建、切换、析构等操作依然廉价,并且可以支持大规模的用户线程并发。而操作系统提供支持的轻量级进程则作为用户线程和内核线程之间的桥梁,这样可以使用内核提供的线程调度功能及处理器映射,并且用户线程的系统调用要通过轻量级线程来完成,大大降低了整个进程被完全阻塞的风险。在这种混合模式中,用户线程与轻量级进程的数量比是不定的,即为N:M的关系。许多UNIX系列的操作系统,如Solaris、HP-UX等都提供了N:M的线程模型实现。
对于Sun JDK来说,它的Windows版与Linux版都是使用一对一的线程模型实现的,一条Java线程就映射到一条轻量级进程之中,因为Windows和Linux系统提供的线程模型就是一对一的。
在Solaris平台中,由于操作系统的线程特性可以同时支持一对一(通过Bound Threads或Alternate Libthread实现)及多对多(通过LWP/Thread Based Synchronization实现)的线程模型,因此在Solaris版的JDK中也对应提供了两个平台专有的虚拟机参数:-XX:+UseLWPSynchronization(默认值)和-XX:+UseBoundThreads来明确指定虚拟机使用哪种线程模型。
现在,让我们跳到Java线程最重要的主题:线程类和可运行接口。
Java的多线程系统建立在Thread类,其方法及其配套接口Runnable的基础上。要创建一个新的线程,你的程序将可以扩展主题或实施的Runnableinterface。
Thread类定义了几种有助于管理线程的方法:
方法 | 意义 |
getName | 获取线程的名称 |
getPriority | 获取线程的优先级 |
isAlive | 确定线程是否仍在运行 |
join |
等待线程终止 |
run | 线程的入口点 |
sleep | 暂停线程一段时间 |
start | 通过调用线程的run方法来启动线程 |
现在,让我们看看如何使用以所有Java程序具有的主要Java线程开头的Thread 。
在这里,我将向您展示如何使用Thread和Runnable接口来创建和管理线程,从Java主线程开始。
为什么主线程如此重要?
Java使您可以通过以下两种方式之一创建线程:
让我们看一下这两种方式如何帮助实现Java线程。
可运行的界面
创建线程的最简单方法是创建一个实现Runnable接口的类。
为了实现Runnable接口,一个类仅需要实现一个名为run()的方法,该方法的声明如下:
public void run( )
在run()内部,我们将定义构成新线程的代码。例子:
public class MyClass implements Runnable {
public void run(){
System.out.println("MyClass running");
}
}
要通过线程执行run()方法,请将MyClass的实例传递给其构造函数中的Thread(Java中的构造函数是类似于在创建对象实例时调用的方法的代码块)。这是完成的方式:
Thread t1 = new Thread(new MyClass ());
t1.start();
当线程启动时,它将调用MyClass实例的run()方法,而不是执行自己的run()方法。上面的示例将打印出文本“ MyClass running ”。
扩展Java线程
创建线程的第二种方法是创建一个扩展Thread的新类,然后重写run()方法,然后创建该类的实例。调用start()之后,线程将执行run()方法。这是创建Java Thread子类的示例:
public class MyClass extends Thread {
public void run(){
System.out.println("MyClass running");
}
}
创建并启动上述线程:
MyClass t1 = new MyClass ();
T1.start();
当run()方法执行时,它将打印出文本“ MyClass running ”。
到目前为止,我们仅使用了两个线程:主线程和一个子线程。但是,我们的程序可以影响所需数量的线程。让我们看看如何创建多个线程。
class MyThread implements Runnable {
String name;
Thread t;
MyThread String thread){
name = threadname;
t = new Thread(this, name);
System.out.println("New thread: " + t);
t.start();
}
public void run() {
try {
for(int i = 5; i > 0; i--) {
System.out.println(name + ": " + i);
Thread.sleep(1000);
}
}catch (InterruptedException e) {
System.out.println(name + "Interrupted");
}
System.out.println(name + " exiting.");
}
}
class MultiThread {
public static void main(String args[]) {
new MyThread("One");
new MyThread("Two");
new NewThread("Three");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
System.out.println("Main thread Interrupted");
}
System.out.println("Main thread exiting.");
}
}
该程序的输出如下所示:
New thread: Thread[One,5,main]
New thread: Thread[Two,5,main]
New thread: Thread[Three,5,main]
One: 5
Two: 5
Three: 5
One: 4
Two: 4
Three: 4
One: 3
Three: 3
Two: 3
One: 2
Three: 2
Two: 2
One: 1
Three: 1
Two: 1
One exiting.
Two exiting.
Three exiting.
Main thread exiting.
这就是Java中的多线程工作方式。各位看官,如果对你有帮助的话三连:点赞+评论+收藏!
参考文档:Java线程模型 - kaleidoscopic