十分钟搞定Java多线程-这样回答“Thread和Runnable的区别”很酷,面试官会对你刮目相看

5 篇文章 0 订阅
5 篇文章 0 订阅

Java提供了多线程来并行执行任务(代码),你需要线程来并行运行多个任务,例如,在后台下载一个文件,并在前端显示进度条。有两种方法可以在Java中创建线程,一是继承java.lang.Thread,二是实现java.lang.Runnable接口。面试官也非常喜欢问这个问题,创建Java线程继承Thread类和实现Runnable接口有什么区别以及如何选择,小问题蕴含大道理。本文中,我们将探讨Thread和Runnable的区别,以及如何回答这个问题。

你可能这样回答:

如果我们继承Thread类创建线程,我们将失去扩展另一个类的机会, 但是,如果是实现Runnable接口,我们就还有继承另一个类的机会,因为Java支持类的单继承,不支持多继承,但支持实现多个接口。

没错!

尽管上述回答是正确的,但它并不是一个令人印象深刻的答案。如果你是一个有经验的coder,放眼四海皆准的回答,让面试官对你评价,就是你面试前刷题库了。虽然我们确实是刷题库了,刷题库确实也是有效的学习方式,但是想要脱颖而出, 为何不刷更加漂亮得答案。面试官认为你有非常清晰自己的见解,看出你的回答有明显的亮点,给你更高评分而且可能很快会转换到其他问题。

一、继承Thread和实现Runnable接口的区别

继承Thread和实现Runnable接口的区别从以下四点进行展开描述:

  1. 线程对象
  2. 线程的内存消耗
  3. 线程在多重继承的情况下
  4. 重写线程方法

1)线程对象

当我们通过继承Thread创建线程时,每个线程都会创建线程类的唯一对象。例如,如果您创建了5个线程,那么内存中就有5个不同的对象。

当我们实现Runnable接口时,我们只创建一个线程类对象,我们传递给多线程的是同一个对象。也就说,所有线程共享同一个线程类对象。

我们通过一个线程示例来理解它们。

a)继承Thread创建线程

在下面的Java代码示例中,我们在main()方法中创建了两个线程t1和t2, class Task是线程类。

注意,对于我们创建的每个线程,我们必须在内存中为其创建不同的对象,例如:

// thread 1
Task t1 = new Task();
// thread 2
Task t2 = new Task();

下面是完整代码示例:

public class Thread4 {

    public static void main(String[] args) {
        // thread 1
        Task t1 = new Task();
        t1.start();
        // thread 2
        Task t2 = new Task();
        t2.start();

    }

}

//task thread class
class Task extends Thread {

    public void run() {
        System.out.println("Thread name is : " + currentThread().getName());
    }
}

输出结果:

Thread name is : Thread-1
Thread name is : Thread-0

b)实现Runnable接口时创建线程

下面的示例与上面的相同,但是使用的是runnable接口。注意,我们只创建了线程类任务的一个对象,例如:Task r = new Task(),并将相同的对象r传递给两个不同的线程t1和t2。换句话说,t1和t2共享线程类“Task”的相同对象。

public class Thread5 {

    public static void main(String[] args) {

        //创建Task线程类
        Task r = new Task();

        // 创建 thread 1
        Thread t1 = new Thread(r);
        t1.start();

        Thread t2 = new Thread(r);
        t2.start();

    }

}

class Task implements Runnable {

    public void run() {
        System.out.println("Runnable thread is running");
    }

}

输出结果:

Runnable thread is running
Runnable thread is running

2)线程的内存消耗

从上面的代码示例我们已经看到,如果是继承Thread类创建多线程,那么我们必须创建线程类“Task”的多个对象。但是,在使用Runnable接口的例子中,只创建了一个对象,并且将线程类“Task”一个对象共享给多个线程。

因此,如果线程类比较重量级(占用大量内存),那么最好使用Runnable接口。

不过,如果你不了解如何使用Runnable Interface来节省内存,请阅读文末给出的详细说明。

3)线程在多重继承的情况下

前面我们提过,如果继承Thread类创建线程,我们就失去了继承另一个类的机会,但是如果我们实现Runnable接口,我们就可以保留继承另一个类的机会。Java不支持使用类的多继承,但是支持实现多接口。

4)重写线程方法

Sometimes we need to override the methods of Thread class. For example, start () method to write our own code inside that, then Extending thread class is option, whereas, it is not possible with Runnable interface.

有时我们需要重写Thread类的方法,例如,重写start()方法并在其中加入我们自己的代码,这时我们需要继承Thread类,因为使用Runnable接口是不能实现该需求的。

为什么需要在Java线程中重写start()方法?因为我们可能想要线程启动之前初始化一些东西,可能要调用一些方法,初始一些任务,然后再开始一个新的线程。在这种情况下,我们必须重写start()方法。

二、总结

1)继承Thread类与实现Runnable接口的区别。

  • 实现Runnable接口,还可以继承另一个类, 而继承Thread类创建线程则不能再继承别的类了。
  • 在有创建多个线程需求的情况下,使用Runnable接口可以节省内存,因为 我们不必创建线程类的多个对象。
  • 通过继承Thread类可以实现方法重写功能,而Runnable接口无法实现。

2)我们如何选择

首选 Runnable 接口:

  • 当不想失去继承其他类的机会时。
  • 当不需要重写Thread类的方法时。
  • 执行多线程任务消耗比较多的内存时。

3)继承Thread类和实现Runnable接口的内存消耗计算

有的人可能一时半会不能理解在上面的例子中我们是如何节省内存的,因为在这两种情况下创建的对象总数都是3。

举个简单的例子来说明一下。

严格来说,我们不能直接根据程序中对象计数的总数来统计内存消耗。

在这两种情况下(任务+线程类),对象总数是相同的。但是,我们需要考虑类的大小。

举个例子:

假设“ Thread”类和“ Task”类的两个类的大小。

Thread类大小: 10 K

Task类大小: 100 K, 因为它可能包含50个变量int, float和double等。

假设我们在程序中创建了20个线程。然后在创建20个线程时,

如果继承Thread类–你创建20个任务类对象,结果大小 =  20 * 100k = 2000k

在实现Runnable接口的情况下:创建1个Task类对象和20个Thread类对象,结果大小为1 * 100k + 20 * 10K = 100k + 200k = 300K。

因此,对于相同的需求,在扩展Thread类时,总内存大小为2000k,而在实现Runnable接口时,总内存大小仅仅为300K。

因此,得出结论:如果实现Runnable接口,会更节省内存

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值