开发技能Java线程并发多线程入门

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 的线程调度策略。 即便有时候让出了控制权,其他线程也不一定会执行。

总结

Built with LogoFlowershow