Java™ 教程(Thread对象)

37次阅读

共计 4853 个字符,预计需要花费 13 分钟才能阅读完成。

Thread 对象
每个线程都与 Thread 类的实例相关联,使用 Thread 对象创建并发应用程序有两种基本策略。

要直接控制线程的创建和管理,只需在每次应用程序需要启动异步任务时实例化 Thread。
要从应用程序的其余部分抽象线程管理,请将应用程序的任务传递给 executor。

本节介绍 Thread 对象的使用,Executors 将与其他高级并发对象一起讨论。
定义和启动线程
创建 Thread 实例的应用程序必须提供将在该线程中运行的代码,有两种方法可以做到这一点:
提供 Runnable 对象,Runnable 接口定义了一个单独的 run 方法,用于包含在线程中执行的代码,Runnable 对象被传递给 Thread 构造函数,如 HelloRunnable 示例中所示:
public class HelloRunnable implements Runnable {

public void run() {
System.out.println(“Hello from a thread!”);
}

public static void main(String args[]) {
(new Thread(new HelloRunnable())).start();
}

}
子类化 Thread,Thread 类本身实现了 Runnable,尽管它的 run 方法什么都不做,应用程序可以子类化 Thread,提供自己的 run 实现,如 HelloThread 示例中所示:
public class HelloThread extends Thread {

public void run() {
System.out.println(“Hello from a thread!”);
}

public static void main(String args[]) {
(new HelloThread()).start();
}

}
请注意,两个示例都调用 Thread.start 以启动新线程。
你应该使用哪个风格?使用 Runnable 对象的第一个风格更通用,因为 Runnable 对象可以继承 Thread 以外的类。第二个风格在简单的应用程序中更容易使用,但受限于你的任务类必须是 Thread 的后代这一事实。本课重点介绍第一种方法,该方法将 Runnable 任务与执行任务的 Thread 对象分开,这种方法不仅更灵活,而且适用于后面介绍的高级线程管理 API。
Thread 类定义了许多对线程管理有用的方法,这些包括静态方法,它们提供关于调用该方法的线程的信息,或影响该线程的状态。其他方法是从管理线程和 Thread 对象的其他线程调用的,我们将在以下部分中研究其中一些方法。
用 Sleep 暂停执行
Thread.sleep 导致当前线程暂停执行指定的时间段,这是使处理器时间可用于应用程序的其他线程或可能在计算机系统上运行的其他应用程序的有效方法。sleep 方法也可以用于调步,如下面的示例所示,和等待具有被理解为具有时间要求的职责的另一个线程,如稍后部分中的 SimpleThreads 示例。
提供了两个重载版本的 sleep:一个指定毫秒的睡眠时间,一个指定纳秒的睡眠时间。但是,这些睡眠时间并不能保证精确,因为它们受到底层操作系统提供的设施的限制,此外,睡眠周期可以通过中断终止,我们将在后面的部分中看到。在任何情况下,你都不能设想调用 sleep 会准确地在指定的时间段内暂停该线程。
SleepMessages 示例使用 sleep 以四秒为间隔打印消息:
public class SleepMessages {
public static void main(String args[])
throws InterruptedException {
String importantInfo[] = {
“Mares eat oats”,
“Does eat oats”,
“Little lambs eat ivy”,
“A kid will eat ivy too”
};

for (int i = 0;
i < importantInfo.length;
i++) {
//Pause for 4 seconds
Thread.sleep(4000);
//Print a message
System.out.println(importantInfo[i]);
}
}
}
请注意,main 声明抛出 InterruptedException,这是一个异常,当 sleep 处于活动状态时,另一个线程中断当前线程时,sleep 将抛出,由于此应用程序尚未定义另一个导致中断的线程,因此无需捕获 InterruptedException。
中断
中断是指示线程应该停止正在做的事情,并执行其他操作,由程序员决定线程如何响应中断,但用于终止线程是很常见的,这是本课程中强调的用法。
线程通过调用 Thread 对象上的 interrupt 来发送中断,以便线程被中断,为使中断机制正常工作,被中断的线程必须支持自己的中断。
支持中断
线程如何支持自己的中断?这取决于它目前正在做什么,如果线程经常调用抛出 InterruptedException 的方法,它只会在捕获该异常后从 run 方法返回。例如,假设 SleepMessages 示例中的中心消息循环位于线程的 Runnable 对象的 run 方法中,然后可以按如下方式修改它以支持中断:
for (int i = 0; i < importantInfo.length; i++) {
// Pause for 4 seconds
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
// We’ve been interrupted: no more messages.
return;
}
// Print a message
System.out.println(importantInfo[i]);
}
许多抛出 InterruptedException 的方法(例如 sleep)被设计为收到中断时取消当前操作并立即返回。
如果一个线程长时间运行而不调用抛出 InterruptedException 的方法呢?那么它必须定期调用 Thread.interrupted,如果收到中断,则返回 true,例如:
for (int i = 0; i < inputs.length; i++) {
heavyCrunch(inputs[i]);
if (Thread.interrupted()) {
// We’ve been interrupted: no more crunching.
return;
}
}
在这个简单的例子中,代码只是测试中断,如果收到中断则退出线程,在更复杂的应用程序中,抛出 InterruptedException 可能更有意义:
if (Thread.interrupted()) {
throw new InterruptedException();
}
这允许中断处理代码集中在 catch 子句中。
中断状态标志
中断机制使用称为中断状态的内部标志来实现,调用 Thread.interrupt 设置此标志,当线程通过调用静态方法 Thread.interrupted 来检查中断时,将清除中断状态,非静态 isInterrupted 方法,由一个线程用于查询另一个线程的中断状态,不会更改中断状态标志。
按照惯例,任何通过抛出 InterruptedException 退出的方法都会在执行此操作时清除中断状态,但是,通过另一个线程调用中断,总是可以立即再次设置中断状态。
加入
join 方法允许一个线程等待另一个线程的完成,如果 t 是其线程当前正在执行的 Thread 对象:
t.join();
导致当前线程暂停执行,直到 t 的线程终止,join 重载方法允许程序员指定等待周期,但是,与 sleep 一样,join 依赖于 OS 进行计时,因此你不应该设想 join 将准确地等待你指定的时间。
与 sleep 一样,join 通过 InterruptedException 退出来响应中断。
SimpleThreads 示例
以下示例汇总了本节的一些概念,SimpleThreads 由两个线程组成。第一个是每个 Java 应用程序都有的主线程,主线程从 Runnable 对象 MessageLoop 创建一个新线程,并等待它完成,如果 MessageLoop 线程需要很长时间才能完成,主线程会中断它。
MessageLoop 线程打印出一系列消息,如果在打印完所有消息之前被中断,MessageLoop 线程将打印一条消息并退出。
public class SimpleThreads {

// Display a message, preceded by
// the name of the current thread
static void threadMessage(String message) {
String threadName =
Thread.currentThread().getName();
System.out.format(“%s: %s%n”,
threadName,
message);
}

private static class MessageLoop
implements Runnable {
public void run() {
String importantInfo[] = {
“Mares eat oats”,
“Does eat oats”,
“Little lambs eat ivy”,
“A kid will eat ivy too”
};
try {
for (int i = 0;
i < importantInfo.length;
i++) {
// Pause for 4 seconds
Thread.sleep(4000);
// Print a message
threadMessage(importantInfo[i]);
}
} catch (InterruptedException e) {
threadMessage(“I wasn’t done!”);
}
}
}

public static void main(String args[])
throws InterruptedException {

// Delay, in milliseconds before
// we interrupt MessageLoop
// thread (default one hour).
long patience = 1000 * 60 * 60;

// If command line argument
// present, gives patience
// in seconds.
if (args.length > 0) {
try {
patience = Long.parseLong(args[0]) * 1000;
} catch (NumberFormatException e) {
System.err.println(“Argument must be an integer.”);
System.exit(1);
}
}

threadMessage(“Starting MessageLoop thread”);
long startTime = System.currentTimeMillis();
Thread t = new Thread(new MessageLoop());
t.start();

threadMessage(“Waiting for MessageLoop thread to finish”);
// loop until MessageLoop
// thread exits
while (t.isAlive()) {
threadMessage(“Still waiting…”);
// Wait maximum of 1 second
// for MessageLoop thread
// to finish.
t.join(1000);
if (((System.currentTimeMillis() – startTime) > patience)
&& t.isAlive()) {
threadMessage(“Tired of waiting!”);
t.interrupt();
// Shouldn’t be long now
// — wait indefinitely
t.join();
}
}
threadMessage(“Finally!”);
}
}

上一篇:进程和线程

正文完
 0