Java中每个线程都和一个Thread
实例关联。使用线程对象创建一个并发应用有两种策略:
- 直接控制线程的创建和管理,每次应用要执行一个异步任务时,就实例化一个
Thread
- 在应用中抽象出线程管理,然后传递应用的任务给
executor
去执行
这一章只介绍Thread
,executor
放在后面的章节介绍。
定义和开始一个线程
一个应用实例化一个Thread
的时候必须要提供这个线程要执行的代码。有以下两种方式:
- 提供一个
Runnable
对象。Runnable
接口里面只有一个方法run
,要执行的代码在放在该方法里面。将该Runnable对象传递给Thread构造器:
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
方法:
public class HelloThread extends Thread {
public void run() {
System.out.println("Hello from a thread!");
}
public static void main(String args[]) {
(new HelloThread()).start();
}
}
这两种方法的区别其实就是接口和继承的区别。第一种方法更通用一些,不仅是因为更灵活,还因为它适用于后面要介绍的高级线程管理API。
Thread
定义了很多方法来管理线程,包括一些静态方法,用来查看或修改线程的状态;还有一些方法用来管理线程。接下去我们简单介绍一些常用的方法。
Pausing Execution with 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]);
}
}
}
首先注意到sleep
是个静态方法;其次需要注意到main
函数声明会抛出InterruptedExcetion
异常,当一个正在sleep的线程被interrupt的时候,sleep
就会抛出该异常。
Interrupts
An interrupt is an indication to a thread that it should stop what it is doing and do something else. It's up to the programmer to decide exactly how a thread responds to an interrupt, but it is very common for the thread to terminate.
A thread sends an interrupt by invoking
interrupt
on theThread
object for the thread to be interrupted. For the interrupt mechanism to work correctly, the interrupted thread must support its own interruption.
那线程如何support its own interruption呢?这取决于该线程当前正在做什么。如果线程经常执行像sleep
这样会抛出InterruptedException
的函数,当捕捉到异常的时候,一般直接从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]);
}
那如果线程很长一段时间都不会调用像sleep
这样会抛出InterruptedException
的函数,那我们就要自己去检查线程是否已经被中断。可以通过调用Thread.interrupted
来判断,比如:
for (int i = 0; i < inputs.length; i++) {
heavyCrunch(inputs[i]);
if (Thread.interrupted()) {
// We've been interrupted: no more crunching.
return;
}
}
注意: 调用了Thread.interrupted()
之后,线程的中断状态会被清除。具体看下面的解释:
The Interrupt Status Flag
The interrupt mechanism is implemented using an internal flag known as the interrupt status. Invoking
Thread.interrupt
sets this flag. When a thread checks for an interrupt by invoking the static methodThread.interrupted
, interrupt status is cleared. The non-staticisInterrupted
method, which is used by one thread to query the interrupt status of another, does not change the interrupt status flag.By convention, any method that exits by throwing an InterruptedException clears interrupt status when it does so. However, it's always possible that interrupt status will immediately be set again, by another thread invoking interrupt.
Joins
join
方法能够让一个线程等待另一个线程执行完。比如你在线程t1中调用了t2.join()
,那么t1会等待t2执行完之后再执行。
join
还可以接受一个参数,指明等待多长时间,和sleep
一样,当收到中断信号时,该方法会抛出异常。需要注意的是,join
和sleep
一样,不能保证等待的时间完全准确。
举例
最后,来看一个例子,复习一下这个章节的知识点:
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!");
}
}
main线程创建了一个MessageLoop线程,然后每隔一秒就调用isAlive
方法检查MessageLoop线程是否已经结束,如果还未结束并且已经没有了耐心,就调用interrupt
方法给MessageLoop线程发中断信号;如果还未结束并且还有耐心,就调用join
函数,等待MessageLoop线程一秒。
网友评论