多线程入门
Thread 类的 run() 和 start() 方法
在 Java 中,Thread 类的 run() 和 start() 方法有本质区别,理解这一点是多线程编程的基础。
核心区别
start():用于启动一个新线程。调用start()后,JVM 会创建一个新的执行线程,并在该线程中自动调用run()方法。多个线程之间并发执行。run():就是一个普通实例方法。直接调用run()不会创建新线程,而是在当前调用线程中同步执行run()方法体内的代码,相当于普通的方法调用。
原理简述
start()是一个native方法,它负责创建操作系统级别的线程,并让该线程执行run()。run()由Runnable接口定义,Thread类实现了它。如果不重写,Thread.run()默认会调用传入的Runnable对象的run()方法。
总结
- 想实现多线程并发,必须调用
start()。 - 直接调用
run()只是普通方法调用,不会并发。 - 牢记:
start()启动线程,run()定义线程要执行的任务。
继承 Thread 的方法和实现 Runnable 接口的方式创建多线程优缺点对比
| 维度 | 继承 Thread | 实现 Runnable |
|---|---|---|
| 资源/代码复用 | 每个线程都是一个独立的子类实例,不能共享同一个任务实例(除非用静态变量) | 多个线程可以共享同一个 Runnable 实例,方便共享数据(例如卖票系统的剩余票数) |
| 单继承限制 | Java 是单继承,一旦继承了 Thread 就不能再继承其他类 | 可以继承其他类,更灵活 |
| 耦合度 | 任务代码与线程运行机制耦合在一起 | 将“任务”与“执行任务的线程”分离,更符合单一职责原则 |
| 线程池支持 | ExecutorService 等线程池框架接受 Runnable 或 Callable,不接受直接提交 Thread 子类 | 原生支持,可直接提交到线程池 |
| 代码风格 | 相对简单,但不利于扩展 | 稍微多写一行 new Thread(runnable),但更清晰 |
为什么推荐实现 Runnable 接口?
(1) 避免单继承的限制
如果你的类已经继承了另一个父类(比如某个业务基类),就无法再继承 Thread。而实现接口可以同时继承一个类并实现多个接口。
(2) 更好的数据共享
多个线程执行同一个任务时(例如多窗口卖票),使用 Runnable 可以共享同一个任务对象,从而自然共享实例变量。用继承 Thread 则需要借助 static 变量,容易引发线程安全问题且不够优雅。示例:卖票系统
// 使用 Runnable
class TicketSeller implements Runnable {
private int tickets = 100;
@Override
public void run() {
while (tickets > 0) {
// 卖票逻辑
}
}
}
// 创建多个线程,共享同一个 seller
TicketSeller seller = new TicketSeller();
new Thread(seller).start();
new Thread(seller).start();
如果用继承 Thread,每个线程都有自己独立的 tickets 变量,必须声明为 static 才能共享,增加了复杂性。
(3) 任务与线程解耦
Runnable 代表“需要执行的任务”,Thread 代表“执行任务的载体”。解耦后,你可以轻松地将同一个 Runnable 提交给线程池、直接运行(run())、或者包装成定时任务,而无需改变任务本身的代码。
(4) 配合线程池更自然
现代 Java 多线程编程中,几乎不直接手动创建 Thread 对象,而是使用 ExecutorService。这些框架都基于 Runnable 或 Callable 设计,不鼓励直接使用 Thread 子类。
控制线程的其他方法
sleep
使当前正在执行的线程暂停指定的毫秒数,也就是进入休眠的状态。 需要注意的是,sleep 的时候要对异常进行处理。
join
等待这个线程执行完才会轮到后续线程得到 cpu 的执行权,使用这个也要捕获异常。
setDaemon
将此线程标记为守护线程,准确来说,就是服务其他的线程,像 Java 中的垃圾回收线程,就是典型的守护线程。 如果其他线程都执行完毕,main 方法(主线程)也执行完毕,JVM 就会退出,也就是停止运行。如果 JVM 都停止运行了,守护线程自然也就停止了。
yield
该方法是一个静态方法,用于暗示当前线程愿意放弃其当前的时间片,允许其他线程执行。然而,它只是向线程调度器提出建议,调度器可能会忽略这个建议。具体行为取决于操作系统和 JVM 的线程调度策略。 即便有时候让出了控制权,其他线程也不一定会执行。
总结
