Java多线程的实现方式到底有几种? 2种?3种?1种?

Java实现多线程有几种方式?相信有工作经验的朋友在面试时都遇到过这个问题?但是网上的说法不一;有说两种、有说三种、也有说四种的。那么到底有几种呢?本文我们来讨论一下这个问题。

1.官方说法

对于这个问题网上说法不一,说几种的都有;所以我们看一下Oracle的官文档

图1.Oracle官方对Thread类的说明

如上图所见 Oracle官方在Thread类的描述中提到,实现线程的方式有两种一种是继承Thread、一种是实现Rnnable接口;Oracle的官方文档相信我们Java开发者都是认同的。这应该是属于业界的权威资料。所以这个回答可以说是标准答案了。

那么这两种方式有什么特点呢?在实践中我们应该是使用哪种方式呢?来看看它们的特性

1.2 继承Thread(不推荐)

代码1:继承Thread实现多线程

public class ThreadImpl extends Thread{

    @Override
    public void run() {
        System.out.println("继承Thread实现多线程...");
    }

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

}
  • 优点
    • 编码简单
  • 缺点
    • 扩展性差,Java只支持单继承,继承了Thread就不能继承其他类

1.3 实现Runnable(推荐)

代码2:Runnable实现多线程

public class RunnableImpl  {

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

}

class MyRunnable implements Runnable {

    @Override
    public void run() {
        System.out.println("Runnable接口实现的多线程");
    }
}
  • 优点
    • 线程声明周期和业务逻辑解耦
    • 结合线程池可节约系统资源消耗,提高性能
    • 拓展性好,实现了Runnable接口还可以实现其他接口或继承其他类。
  • 缺点
    • 相比于继承Thread编码稍显复杂

综上所述,如无特殊要求;一般情况下推荐实现Runnable,其良好的拓展性和解耦特性;可以适应绝大多数场景。如果你只是写一个小demo或者有特殊要求可以使用继承Thread,否则都是推荐使用实现Runnable。

2.本质区别

以上都是从宏观代码层面上的区别和特性,想要知道这两种方式的最本质的区别还是得去看相关的源码

代码3: java.lang.Thread.java

public class Thread implements Runnable {

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

    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
    //...
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

}

以上是java.lang.Thread的源码节选,我省略掉了部分内容,仅仅保留了对本文主题有参考价值的部分。

可见Thread也是实现了Runnable中的run()方法,而Thread提供的有参构造Thread(Runnable target)恰恰就是上文代码2:Runnable实现多线程中使用的方法;该构造传入一个Runnable实现类赋值给Thread中的属性target。

线程启动后执行run()方法,如果target不为空,就执行传进来的target中的run()方法。也就是本文的例子中MyRunnable中的run()方法。

再看上文代码1。
代码1:继承Thread实现多线程

public class ThreadImpl extends Thread{

    @Override
    public void run() {
        System.out.println("继承Thread实现多线程...");
    }

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

}

ThreadImpl继承Thread类,重写了其中的run()方法。因此Thread中判断target的代码被我们重写的覆盖;当线程启动后执行的代码就是我们重写后的run()方法。

3.实现方式究竟有几种?

回到标题,那么实现多线程的方式究竟有几种呢?如果面试中遇到这个问题,我们可以这么回答:

一般来说有两种;一种是继承Thread一种是实现Runnable。这是Oracle官方文档中的描述。如果你对Java多线程技术了解的比较多,可以说还有线程池等方式可以实现多线程。然后话锋一转:不管是哪种方式,其实都是实现Runnable接口这一种方式,在上文2.本质区别中;笔者从源码层面简单的分析出Thread类其实也是实现了Runnable。所以上文说的两种实现方式可以看作都是实现了Runnable,只是实现方式有所不同。事实上包括线程池等实现线程池的方式,其实从本质上也是实现了Runnable接口。

因此在从这个角度讲我们可以说实现多线程的方式本质上只有一种,但是可以有不同的设计或实现思路。

4.两种方式同时使用会发生什么?

public class TogetherThread {

    public static void main(String[] args) {
        // <A> Runnable 匿名内部类
        Thread thread = new Thread(() -> System.out.println("Runnable中的run()")) {
            // <B> 重写Thread 中的 run() 方法
            @Override
            public void run() {
                System.out.println("Thread中的run()");
            }
        };
        thread.start();
    }
}

以上代码中,<A>使用匿名内部类实现了Runnable接口传入Thread有参构造器中,然后<B>又重写了Thread的run()方法。朋友们可以自己先自己想一想运行结果应该是什么?

看到这里有的朋友可能要说了:<A>处匿名内部类实现了Runnable接口传入Thread有参构造器那Thread中的target变量就不为null了线程启动后就直接执行target中的run()方法了也就是匿名内部类中的run()方法。ok,先让我们看看答案:


答案显而易见,这样想的朋友猜错了。

这个问题我们应该从从面向对象的角度来分析,虽然我们在<A>处构造了一个匿名内部类传入了target属性,但是<B>处直接覆盖了Thread中的run()方法。即使传入了target;也无法执行其中的run()方法。

因此线程启动后执行的是<B>处Thread被重写的方法

5.线程池、FutureTask、Timer、匿名内部类、Lambda表达式

事实上,不管是线程池、Callable、FutureTask还是定时器Timer;本质上都是对Thread或者Runnable做的包装。底层的源码实现还是Thread和Runnable

线程池部分源码

FutureTask类图

TimerTask部分源码

而匿名内部类和Lambda表达式都只是语法的另一种表现形式本质上还是实现了Runnable

本人能力有限,本文只是我个人的技术分享。如有错误或不实之处;恳请大家不吝赐教。可以在公众号留言讨论;本人定虚心学习。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值