Java 线程就像一个可以在 Java 应用程序内执行 Java 代码的虚拟 CPU。当 Java 应用程序启动时,其 main()
方法由主线程执行。主线程是 Java 虚拟机创建的执行应用程序的特殊线程。从应用程序内部可以创建和启动更多线程,这些线程可以与主线程并行执行应用程序的不同部分。
Java 线程是与其它 Java 对象一样的对象。线程是 java.lang.Thread
类的实例,或者这个类的子类的实例。除了作为对象以外,Java 线程还可以执行代码。本文将解释如何创建和启动线程。
创建和启动线程
Java 中像下面这样创建一个线程:
Thread thread = new Thread();
要启动 Java 线程,我们要像下面这样,调用线程的 start()
方法:
thread.start();
本例没有为线程指定任何要执行的代码,所以线程启动后会立即再次停止。
有两种方法可以指定线程应该执行的代码。第一种方法是创建 Thread
的子类,并重写 run()
方法。第二种方法是传递一个实现了 Runnable
(java.lang.Runnable
)的对象给 Thread
构造器。下面将介绍这两种方法。
Thread 子类
指定线程要运行的代码的第一种方法是创建 Thread 的子类,并重写 run()
方法。run()
方法就是在调用 start()
之后线程要执行的代码。如下是一个创建 Thread
子类的示例:
public class MyThread extends Thread {
public void run(){
System.out.println("MyThread running");
}
}
可以像下面这样,创建和启动上述线程:
MyThread myThread = new MyThread();
myTread.start();
线程启动后,start()
调用会返回。它不会等到 run()
方法执行完成。run()
方法会像在不同的 CPU 上执行一样执行。当 run()
方法执行时,会打印出文本 "MyThread running"
。
还可以像下面这样,创建一个 Thread
的匿名子类:
Thread thread = new Thread(){
public void run(){
System.out.println("Thread Running");
}
}
thread.start();
一旦 run()
被新线程执行,本示例就打印出文本 "Thread running"
。
Runnable 接口实现
执行线程应该执行的代码的第二种方法是创建一个实现了 java.lang.Runnable
接口的类。实现了 Runnable
接口的 Java 对象可以被 Java Thead
执行。如何实现会在本教程稍后展示。
Runnable
接口是 Java 平台带的一个标准 Java 接口,该接口只有一个方法 run()
。如下基本是 Runnable
接口的样子:
public interface Runnable() {
public void run();
}
无论线程在执行时应该做什么,都必须包含在 run()
方法的执行中。实现 Runnable
接口有三种方式:
- 创建一个实现
Runnable
接口的类。 - 创建一个实现
Runnable
接口的匿名类。 - 创建一个实现
Runnable
接口的 Lambda。
这三种选项在下面的小节中解释。
实现 Runnable 接口的类
第一种实现 Runnable
接口的方式是创建一个实现 Runnable
接口的自定义类。如下是示例:
public class MyRunnable implements Runnable {
public void run(){
System.out.println("MyRunnable running");
}
}
Runnable
实现所做的就是打印出文本 MyRunnable running
。在打印该文本后,run()
方法退出,执行 run()
方法的线程会停止。
实现 Runnable 接口的匿名类
还可以创建 Runnable
的匿名实现。如下是一个实现 Runnable
接口的匿名类:
Runnable myRunnable =
new Runnable(){
public void run(){
System.out.println("Runnable running");
}
}
除了是一个匿名类外,本示例与使用实现 Runnable
接口的自定义类示例很相似。
实现 Runnable 接口的 Java Lambda
实现 Runnable
接口的第三种方式是创建一个实现了 Runnable
接口的 Lambda。这是可能的,因为 Runnable
接口只有一个未实现的方法,所以实际上(尽管可能是无意中的)是一个函数式 Java 接口。
如下是实现了 Runnable
接口的 Lambda 表达式的一个示例:
Runnable runnable =
() -> { System.out.println("Lambda Runnable running"); };
启动实现 Runnable 接口的线程
为了拥有被线程执行的 run()
方法,应该传递一个实现了 Runnable
接口的类、匿名类或者 Lambda 表达式的实例到一个 Thread
的构造器。如下是示例:
Runnable runnable = new MyRunnable(); // 或者一个匿名类、或者 Lambda...
Thread thread = new Thread(runnable);
thread.start();
当线程启动时,它会调用 MyRunnable
实例的 run()
方法,而不是它自己的 run()
方法。上述示例会打印出文本 `”MyRunnable running”。
子类还是 Runnable?
关于这两种方法中哪种是最好的,并没有一个确定的答案,二者都可以满足要求。不过,就我个人而言,更倾向于实现 Runnable
接口,然后把实现的实例交给一个 Thread
实例。因为当 Runnable
实例是由线程池执行时,让 Runnable
实例排队等候,直到线程池中的线程空闲,实现起来很容易。而用 Thread
子类就有点困难。
有时我可能必须同时实现 Runnable
接口和 Thread
子类。例如,创建了 Thread
的子类,该子类可以执行多个实现了Runnable
接口的线程。实现线程池时通常就会出现这种情况。
常见错误:调用 run()
而不是 start()
在创建和启动线程时,一个常见的错误就是调用 Thread
的 run()
方法,而不是 start()
,比如:
Thread newThread = new Thread(MyRunnable());
newThread.run(); // 应该是 start();
最开始可能你什么都没注意到,因为 Runnable
的 run()
方法是按预期执行的。不过,run()
方法不是由刚创建的新线程执行的,而是由创建该线程的线程执行的。换句话说,就是执行上述两行代码的线程。为了让 MyRunnable
实例的 run()
方法被新创建的线程 newThread
调用,我们必须调用 newThread.start()
方法。
线程名称
在创建线程时,可以给线程一个名字。名称可以帮助我们区分不同的线程。比如,如果多个线程写入 System.out
,如果线程有名称的话,查看哪个线程写了文本就很方便。如下是一个示例:
Thread thread = new Thread("New Thread") {
public void run(){
System.out.println("run by: " + getName());
}
};
thread.start();
System.out.println(thread.getName());
请注意作为参数传递给 Thread
构造器的字符串 “New Thread”。这个字符串就是线程的名称。名称可以通过 Thread
的 getName()
方法获取。当使用 Runnable
接口时,还可以传递一个名称给 Thread
。比如:
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable, "New Thread");
thread.start();
System.out.println(thread.getName());
不过,请注意,既然 MyRunnable
类不是 Thread
的子类,所以它不能访问执行它的线程的 getName()
方法。
Thread.currentThread()
Thread.currentThread()
方法返回执行 currentThread()
的 Thread
实例的引用。通过这种方式,我们就可以访问表示执行给定代码块的线程的 Java Thread
对象。如下是如何使用 Thread.currentThread()
的一个例子:
Thread thread = Thread.currentThread();
得到了对 Thread
对象的引用后,就可以调用它上面的方法。比如,可以通过执行如下代码,得到当前执行的线程的名称:
String threadName = Thread.currentThread().getName();
线程示例
下面是一个小例子。首先,打印出执行 main()
方法的线程的名称。这个线程是由 JVM 分配的。然后,启动 10 个线程,并给它们一个数字作为线程名("" + i
)。然后每个线程打印出它的名称,然后停止执行。
public class ThreadExample {
public static void main(String[] args){
System.out.println(Thread.currentThread().getName());
for(int i=0; i<10; i++){
new Thread("" + i){
public void run(){
System.out.println("Thread: " + getName() + " running");
}
}.start();
}
}
}
请注意,即使线程是按顺序启动的(1、2、3 等等),它们也可能不会按顺序执行,也就是说,线程1可能不是第一个把它的线程名写到 System.out
的线程。这是因为线程原则上是并行执行的,而不是按顺序执行的。JVM 和操作系统决定线程的执行顺序。这种顺序不必与启动它们的顺序相同。
暂停线程
线程可以通过调用静态方法 Thread.sleep()
来暂停自己。sleep()
以毫秒数为参数,它会尝试在恢复执行之前休眠该微秒数。线程的休眠并非 100% 精确,但还是相当不错的。如下是一个通过调用 Thread.sleep()
方法,暂停一个 Java 线程 3 秒(3000毫秒)的示例:
try {
Thread.sleep(10L * 1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
执行上述代码的线程会休眠大约 10 秒钟(10000毫秒)。
中止线程
终止一个线程需要线程实现代码中做一些准备。Thread
类包含了一个 stop()
方法,但是已经被弃用了。原始的 stop()
方法并不能保证线程被停止时处于什么状态。也就是说,线程执行期间可以访问的所有 Java 对象会处于一种未知状态。如果应用程序中的其它线程也可以访问相同的对象,应用程序就可能会意外地、不可预测地失败。
所以如果要让线程中止,我们得自己实现线程代码,而不是调用 stop()
方法。这里有一个类的示例,该类实现了 Runnable
接口,并包含了一个特殊的方法 doStop()
来通知 Runnable
实例停止。Runnable
会检查这个通知,并在准备好时停止。
public class MyRunnable implements Runnable {
private boolean doStop = false;
public synchronized void doStop() {
this.doStop = true;
}
private synchronized boolean keepRunning() {
return this.doStop == false;
}
@Override
public void run() {
while(keepRunning()) {
// 继续做该线程应该做的。
System.out.println("Running");
try {
Thread.sleep(3L * 1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
请注意 doStop()
和 keepRunning()
方法。doStop()
想被从另一个线程中调用,而不是被执行 MyRunnable
实例的 run()
方法的线程调用。keepRunning()
方法在内部是被执行 MyRunnable
实例的 run()
方法的线程调用。只要 doStop()
还没有被调用,keepRunning()
方法就会返回 true
,也就是说执行 run()
方法的线程会一直运行。
如下是一个示例,在这个示例中,启动一个 Java 线程执行上面 MyRunnable
类的一个实例,然后在一段延迟后再把它停止:
public class MyRunnableMain {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
try {
Thread.sleep(10L * 1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
myRunnable.doStop();
}
}
这个示例首先创建一个 MyRunnable
实例,然后把该实例传给一个线程,并启动该线程。然后执行 main()
方法的线程(主线程)休眠 10 秒,然后调用 MyRunnable
实例的 doStop()
方法。这会导致执行 MyRunnable
方法的线程停止,因为 keepRunning()
会在 doStop()
被调用后返回 false
。
请记住,如果 Runnable
实现需要的不仅仅是 run()
方法(比如,还需要 stop()
或者 pause()
方法),那么就不能再用 Java Lambda 表达式创建 Runnable
实现。Java Lambda 表达式只能实现一个方法。所以,如果要实现多个方法,就必须使用自定义类,或者继承 Runnable
的自定义接口,该接口有额外的方法,能被一个匿名类实现。