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

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

你可能这样回答:java

若是咱们继承Thread类建立线程,咱们将失去扩展另外一个类的机会, 可是,若是是实现Runnable接口,咱们就还有继承另外一个类的机会,由于Java支持类的单继承,不支持多继承,但支持实现多个接口。面试

没错!多线程

尽管上述回答是正确的,但它并非一个使人印象深入的答案。若是你是一个有经验的coder,放眼四海皆准的回答,让面试官对你评价,就是你面试前刷题库了。虽然咱们确实是刷题库了,刷题库确实也是有效的学习方式,可是想要脱颖而出, 为什么不刷更加漂亮得答案。面试官认为你有很是清晰本身的看法,看出你的回答有明显的亮点,给你更高评分并且可能很快会转换到其余问题。ide

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

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

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

1)线程对象

当咱们经过继承Thread建立线程时,每一个线程都会建立线程类的惟一对象。例如,若是您建立了5个线程,那么内存中就有5个不一样的对象。spa

当咱们实现Runnable接口时,咱们只建立一个线程类对象,咱们传递给多线程的是同一个对象。也就说,全部线程共享同一个线程类对象。线程

咱们经过一个线程示例来理解它们。code

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();

    }

}

//实现Runnable接口
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()方法。

2、总结

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接口,会更节省内存 !

相关文章
相关标签/搜索