本文介绍通过实现
Runnable
接口来创建线程的方法,并附上一个简单案例。
一、前言
为什么要实现 Runnable
接口?
-
Java 单继承:如果继承了
Thread
,就无法再继承其他类。而实现接口不会有这个限制。 -
更符合面向对象设计原则:任务和线程分离,任务逻辑和线程执行机制独立,代码更加灵活。(解耦合)
本文以实现Runnable接口的方式,通过一个简单的例子来带大家理解线程的创建和启动流程。
二、示例代码
下列是本文讲解的完整代码:
package thread.test;
// 定义一个类 MyThread3,它实现了 Runnable 接口
class MyThread3 implements Runnable {
// 必须重写 Runnable 接口里的 run 方法
@Override
public void run() {
// 死循环,线程启动后会不停地做下面的事情
while (true) {
System.out.println("hello runnable"); // 打印信息
try {
Thread.sleep(1000); // 让当前线程休息 1000 毫秒(1秒)
} catch (InterruptedException e) {
// 如果休眠过程中被打断了,抛出运行时异常
throw new RuntimeException(e);
}
}
}
}
// 主类,包含主方法 main
public class ThreadDemo3 {
public static void main(String[] args) {
// 创建 MyThread3 对象,并用它来创建一个 Thread 线程对象
Thread myRunnable = new Thread(new MyThread3());
// 启动线程,系统会自动去调用 MyThread3 的 run() 方法
myRunnable.start();
// 主线程本身也进入死循环,不停打印
while (true) {
System.out.println("hello main"); // 打印信息
try {
Thread.sleep(1000); // 主线程每次打印后也休息 1 秒
} catch (InterruptedException e) {
// 主线程在睡眠时如果被打断,同样抛出运行时异常
throw new RuntimeException(e);
}
}
}
}
三、代码详解
1.创建线程类,实现Runnable接口
class MyThread3 implements Runnable {
必须要实现Runnable中的run()方法
2.实现run()方法
@Override
public void run() {
while (true) {
System.out.println("hello runnable");
try {
Thread.sleep(1000); //1000毫秒 (单位毫秒),也就是1秒
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
3.主程序
public class ThreadDemo3 {
public static void main(String[] args) {
Thread myRunnable = new Thread(new MyThread3());
myRunnable.start();
while (true) {
System.out.println("hello main");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
Thread
构造方法
Thread
类的构造方法有多个重载版本。我们关注的是这个构造方法:
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
1. Thread(Runnable target)
-
这个构造方法接收一个
Runnable
类型的对象作为参数。 -
target
就是我们传入的new MyThread3()
。作用: 将我们定义的任务(
MyThread3
中的run()
方法)传递给Thread
对象。
2. init(...)
方法
init(...)
是 Thread
类的一个私有方法,负责对线程进行初始化。具体如下:
private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
this.group = g;
this.target = target;
this.name = name;
this.stackSize = stackSize;
this.daemon = false;
this.priority = Thread.NORM_PRIORITY;
this.name = name;
}
run()
方法的执行
-
当你调用
.start()
时,实际上会触发Thread
类的start()
方法。start()
方法会:-
调用
run()
方法。 -
run()
方法内部会执行我们传递给Thread
对象的Runnable
(就是MyThread3
的run()
方法)。
也就是说,
start()
方法内部的流程大致如下:-
启动线程:通过操作系统的线程管理启动一个新线程。
-
执行
run()
:执行Runnable
中定义的run()
方法,即打印"hello runnable"
。
-
四、程序运行结果
运行后,控制台将交替输出:
hello main
hello runnable
hello main
hello runnable
hello main
hello runnable
hello runnable
hello main
hello runnable
hello main
hello runnable
hello main
hello runnable
hello main
hello runnable
hello main
hello runnable
hello main
hello runnable
hello main
hello runnable
hello main
^C
进程已结束,退出代码为 130 (interrupted by signal 2:SIGINT)
两个“永远循环打印内容”的线程:
-
一个线程不停地打印
"hello runnable"
-
另一个线程不停地打印
"hello main"
这两个线程是同时运行的(而不是一个跑完另一个才跑),所以你在终端上会看到两种打印交替出现!
总结
本文用到的类或方法 | 作用 |
---|---|
Runnable | 定义线程要做什么 |
Thread | 创建线程类的实例 |
start() | 真正创建线程,调用 run() 里的内容 |
sleep(1000) | 让线程睡一秒(1000毫秒) |
try-catch | 捕捉可能出现的异常 |
为什么要用 Runnable?直接继承 Thread 不行吗?
答:可以,但Runnable 更灵活。一个类已经继承了别的类,就不能再继承 Thread,但可以实现 Runnable。
抛出 RuntimeException 是必须的吗?
答:不是的,这只是简单处理。一般正规写法会更优雅,比如直接打个日志或安全退出。