对Thread的start()和run()的一些认识

本文详细解析了Java中创建线程的两种主要方法:重写Thread类和实现Runnable接口,探讨了当同时使用这两种方式时,线程运行的具体机制。通过源码分析,解释了为何只执行了Thread类的run()方法,并提供了使两者都能运行的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

查漏补缺,巩固基础。
通常我们需要新建线程执行操作时,可以这么写:

写法一: 在需要的地方调用new MyThread().start(),并重写匿名Thread类的run方法,如下;

		new Thread() {
			@Override
			public void run() {
				System.out.println("Thread run");
			}
		}.start();

输出:Thread run

写法二: 实现Runnable接口(实现Runnable的run()方法),然后传入Thread的构造方法中,再调用Thread的start()方法,如下:

		new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println("Runnable run");
			}
		}).start();

输出: Runnable run

注意,写法一是重写了Thread的run()方法,而写法二是实现了Runnable接口的run()方法。

那么问题来了:

如果我同时重写Thread和实现Runnable两个run()方法,像下面这样,会怎么样呢?
		new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println("Runnable run");
			}
		} ) {
			@Override
			public void run() {
				System.out.println("Thread run");
			}
		}.start();

实际运行输出:Thread run

为什么Runnable的run方法就没有得到执行了呢?这个就得知道Thread的start()方法运行之后,发生了什么。
如果去看Thread类的start()方法源码,我们会发现它会调用一个native方法start0(),这个方法就不做探究了,它会在底层开启异步线程,并调用此Thread的run()方法。
那么Thread的run()方法,源码里又是怎么实现的呢?贴出来看下:

    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

很简单,直接调用了target对象的run()方法。那么target是什么呢?它正是Thread的构造方法传入的Runnable对象。见Thread的构造方法的源码:

    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }

那么现在捋一下,为什么我们同时重写了Thread的run(),和实现了Runnable接口的run()之后,只有Thread的run()得到运行了?因为原本Thread的run()所做的唯一的事就是调用Runnable对象的run(),而上面我们把Thread的run()给重写了,那自然Runnable对象的run()就得不到执行了。

那有没有办法让这两个run()都得到执行呢?很简单,只要在匿名Thread的run()方法里加一行super.run()即可,因为super就是Thread类。像下面这样;

		new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println("Runnable run");
			}
		} ) {
			@Override
			public void run() {
				super.run(); //添加这一行
				System.out.println("Thread run");
			}
		}.start();

输出结果;

Runnable run
Thread run

另外,如果不用匿名内部类,而是新建了MyThread类继承Thread类,然后使用的时候又想传入Runnable对象的话,那必须给MyThread类重写一个带Runnable类型参数的构造方法,然后在构造方法中调用super(runnable),因为Java中构造方法是不会被继承的。

还有一点:直接调用Thread对象的run()方法,或者直接调用Runnable对象的run()方法,可以开启新线程吗?
答案是不行。这样的调用,是在当前线程运行run()方法,就跟调用普通方法没什么区别,而start()是会开启新线程的。

### Java 中 `Thread` 类的 `run()` 方法与 `start()` 方法的区别及使用场景 #### 1. 基本概念 在 Java 的多线程编程中,`Thread` 类提供了两个核心方法:`run()` `start()`。这两个方法的功能用途有显著差异。 - **`start()` 方法** 当调用 `start()` 方法时,它会触发 JVM 创建一个新的线程并执行该线程的任务[^1]。新线程一旦被创建,JVM 就会在后台自动调用 `run()` 方法来运行线程的具体逻辑。因此,`start()` 是真正用于启动线程的方法。 - **`run()` 方法** 这是一个普通的实例方法,通常由开发者重以定义线程要执行的具体任务。如果直接调用 `run()` 方法而不是通过 `start()` 启动线程,则其行为类似于普通方法调用,在当前线程上下文中执行,而不会开启新的线程[^3]。 #### 2. 工作机制对比 当调用 `start()` 方法时,JVM 执行以下操作: - 初始化新线程的相关资源。 - 切换到就绪状态等待 CPU 调度。 - 在获得调度权后,进入运行状态并通过内部机制调用 `run()` 方法完成实际工作[^4]。 然而,直接调用 `run()` 不涉及上述过程;它只是简单地作为常规函数被执行,无法实现真正的并发处理[^2]。 #### 3. 使用场景分析 以下是两种方法适用的不同情况: - **使用 `start()`** - 需要在独立的新线程里执行某些耗时或阻塞的操作。 - 实现程序内的并发控制,提高效率或者分离业务逻辑。 - **直接调用 `run()`** - 测试阶段快速验证 `run()` 内部代码逻辑是否正常运作而不必考虑多线程环境的影响。 - 特定情况下不需要利用额外线程仅需顺序执行某段流程时可采用这种方式简化开发复杂度。 #### 示例代码展示 下面给出一段基于前述理论的实际应用例子说明如何正确运用这两种方式: ```java public class MyTask extends Thread { @Override public void run() { System.out.println(Thread.currentThread().getName()+" says Hello!"); } public static void main(String[] args) throws InterruptedException{ MyTask task=new MyTask(); // 正确做法: 开启子线程 task.start(); /* 输出可能是(取决于CPU调度): Thread-0 says Hello! main continues... */ // 错误示范: 只是在主线程内模拟了单一线程的行为 task.run(); /* 输出总是: main says Hello! */ System.out.println("main continues..."); } } ``` #### 总结 综上所述,理解并区分 `start()` `run()` 对于掌握 Java 多线程至关重要。只有合理选用它们才能充分发挥多核处理器的优势从而构建高效稳定的软件系统。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值