定义并开始一个线程
创造线程实例的应用必须提供将在线程里面运行的代码,有两种方法来实现:
- 提供一个Runnable对象,Runnable接口只定义了一个方法run,用来包含需要在 线程里面执行的代码。这个Runnable对象会被传递到Thread的构造器去,就像下面的HelloRunnable例子里面演示的那样:
- 使用Thread的子类。Thread类实现了Runnable接口,不过它的run方法是空的。应用 可以继承Thread,提供它自己的run方法,就像下面的HelloThread里面演示的那样:
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();
}
}
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类定义了一系列对于线程管理有用的方法,其中包括static方法,来提供调用此方法的线程的信息或者影响调用此方法的线程的一些状态。另外的一些别的线程调用的方法与管理线程和Thread对象有关.
使用Sleep暂停执行
Thread.sleep会导致当前进程挂起执行一段时间。这是一个让出处理器时间给运行在 操作系统上的相同应用的其他线程或者别的应用的一些进程的有效方法。sleep方法 还可以被使用在pacing上,正如下面的例子显示的这样,以4s的间隔打印字符串值。 另外,sleep还可以用来等待别的有时间需求的线程,如后面SleepThreads例子那样。
java提供两种sleep方法的重载形式,一种是毫秒一种是纳秒。然而,睡眠时间不能确 保是准确的,因为它们受到了底层的os的功能限制。另外,就像我们在后面部分可以学 到的,sleep时可以被中断停止。在任何一种情况下,都不能假定调用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方法还是active的时候 别的线程打断了当前线程的结果。因为这个应用并没有定义其他线程来导致这个打断, 所以这里就没必要去catch这个InterruptedException。
Interrupts
中断是线程应该停止当前动作去做其他事的征兆。线程怎样去相应中断是程序员去决定的,但是通常情况下都是选择让线程停止。这也是这部分课程强调的使用方法。线程通过在Thread对象上调用interrupt来传递中断。要让中断机制正确工作,中断的线程必须支持自身中断。
支持中断
线程怎样支持自身中断呢?这取决于它正在进行的动作。如果这个线程频繁调用抛出 InterruptedException的方法,那么它只是在catch异常后从run方法返回。例如,假 设SleepMessages例子中的核心信息循环在一个线程的Runnable对象的run方法里面。 那么它可以被修改成下面这样来支持中断:
Thread thread = new Thread(() -> {
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.
System.out.println("interrupted, =.=");
return;
}
// Print a message
System.out.println(importantInfo[i]);
}
});
thread.start(); // 启动新线程
Thread.sleep(6000); // 主线程睡眠6s
thread.interrupt(); // 打断thread线程
许多像sleep这样抛出InterruptedException的方法是为了在收到中断时取消它们的 当前操作并直接返回。那么如果线程运行了很长时间且没有调用会抛出Interrupted- Exception的方法该怎么办呢?那么它就必须周期性地调用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;
}
// improvement
if (Thread.interrupted()) {
throw new InterruptedException();
}
}
在这个简单例子里面,代码指示测试有没有中断,如果接受到中断就退出线程。在更加复杂的应用里面,可能抛出一个InterruptedException会更合理,这样可以使得中断处理代码集中在catch的clause里面。
中断状态标志
中断机制是利用一个内在标志interrupt status来实现的。调用Thread.interrupt会设置这个标志为1。当线程通过调用static方法Thread.interrupted方法来检查中断时,interrupt status会被清0。而线程用来查询其他线程的中断状态的非static的isInterrupted方法是不会改变interrupt status标志的。
按照惯例,任何通过抛出一个InterruptedException来结束的方法会在此时清掉标志位。然而,标志位却总是有可能因为其他线程调用了interrupt而重新设置。
Joins
join方法允许一个线程等待另一个线程的结束。如果t是一个Thread对象,且此线程正在运行。那么t.join();
会导致当前线程挂起执行直到t的线程结束。join的重载形式允许程序员去指定一个等待时间。然而,正如sleep,join依赖os,所以也不能假定join会等待指定的时间。和sleep一样,join对于中断的相应是抛出InterruptedException。