一、实现多线程到底有多少种方法

实现Java多线程,到底有多少种方法?

Oracle官网:两种

方法一:实现Runnale接口

/**
 * 描述:用Runnable方式创建线程
 *
 * @author Juning
 * @date 2019-08-29 23:41
 */
public class RunnaleStyle implements Runnable {

    public static void main(String[] args) {
        Thread thread = new Thread(new RunnaleStyle());
        thread.start();
    }

    @Override
    public void run() {
        System.out.println("用Runnable方法实现线程");
    }

}

方法二:继承Thread

/**
 * 描述:用Thread方式实现线程
 *
 * @author Juning
 * @date 2019/8/29 23:47
 */
public class ThreadStyle extends Thread {

    @Override
    public void run() {
        System.out.println("用Thread方式实现线程");
    }

    public static void main(String[] args) {
        new ThreadStyle().run();
    }

}

两种方法的对比

Oracle官方文档上说:Thread类有两个常用的构造器(构造函数)

  • 方法一:实现Runnale接口需要一个Runnale参数
  • 方法二:继承Thread类是不需要任何参数的
    那么那种方法更好呢?它们的区别在哪里呢?

方法一(实现Runnale接口)

实现Runnale接口好在哪里?方法二的缺点主要有三个:

  • 解耦
    从代码架构角度:具体的任务(run()方法)应该和“创建和运行线程的机制(Thread类)”解耦,用Runnable对象可以实现解耦。

  • 资源节约
    使用继承Thread的方式的话,那么每次想新建一个任务,只能新建一个独立的线程,而这样做的损耗会比较大(比如重头开始创建一个线程、执行完毕以后再销毁等。如果线程的实际工作内容,也就是run()函数里只是简单的打印一行文字的话,那么可能线程的实际工作内容还不如损耗来的大)。如果使用Runnable和线程池,就可以大大减小这样的损耗。

  • 拓展性不足
    继承Thread类以后,由于Java语言不支持双继承,这样就无法再继承其他的类,限制了可扩展性。

两种方法的本质对比

这两种方法本质上的区别又是什么呢?

  • 方法一:最终调用target.run();
  • 方法二:run()整个都被重写

方法一和方法二,也就是“实现Runnable接口并传入Thread类”和“继承Thread类然后重写run()”在实现多线程的本质上,并没有区别,都是最终调用了start()方法来新建线程。
这两个方法的最主要区别在于run()方法的内容来源:

  /**
     * If this thread was constructed using a separate
     * <code>Runnable</code> run object, then that
     * <code>Runnable</code> object's <code>run</code> method is called;
     * otherwise, this method does nothing and returns.
     * <p>
     * Subclasses of <code>Thread</code> should override this method.
     *
     * @see     #start()
     * @see     #stop()
     * @see     #Thread(ThreadGroup, Runnable, String)
     */
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

二、同时使用两种方法会怎么样

废话不多说,直接上代码:

/**
 * 描述:同时使用Runnable和Thread两种实现线程的方式
 *
 * @author Juning
 * @date 2019/9/16 23:45
 */
public class BothRunnableThread {

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("我来自Runnable");
            }
        }) {
            @Override
            public void run() {
                System.out.println("我来自Thread");
            }
        }.start();
    }

}

结果如下:
WX20190917-000117@2x
从我们的运行结果来看,由于Java语言的特性,虽然new Thread传入了一个Runnable对象,但是当我们重写run()方法的时候,就将原本需要Runnable对象的的run()方法覆盖掉了,所以会出现只有“我来自Thread”的输出结果。

三、最精准的描述

根据以上的探究,在别人问我们实现多线程到底有多少种方法的时候我们可以这样说

  • 通常我们可以分为两种方法,Oracle它也是这么说。
  • 准确的将,创建线程的方式只有一种方法,那就是构造Thread类,而实现线程的执行单元有两种方式:
    • 方法一:实现Runnable接口的run()方法,并把Runnable的实例传给Thread
    • 方法二:重写Threadrun()方法(继承Thread类)

当然还有更准确的说法:

实现多线程一共有两种方法。
他们分别是实现Runnable接口和继承Thread类。
但是,我们看原理,其实Thread类实现了Runnable接口,并且看Thread类的run()方法,会发现其实那两种本质都是一样的。
不管是“继承Thread类然后重写run()”还是“实现Runnable接口并传入Thread类”在实现多线程的本质上,并没有区别,都是最终调用了start()方法来新建线程。
当然还有其他的实现线程的方法,例如线程池等,它们也能新建线程,但是细看源码,从没有逃出过本质,也就是实现Runnable接口和继承Thread类。

四、结论

我们只能通过新建Thread类这一种方式来创建线程,但是类里面的run()方法有两种方式来实现,第一种是重写run()方法,第二种实现Runnable接口的run()方法,然后再把该Runnable实例传给Thread类。除此之外,从表面上看线程池、定时器等工具类也可以创建线程,但是它们的本质都逃不出刚才所说的范围。