两种实现多线程的方式(详细~~~)

多线程的实现

实现方式总共有三种(JDK1.5后增加了第三种: Callable接口),

分别为继承Thread类,实现Runnable或Callable接口

继承Thread类

  • 该类在 java.lang.Thread下,从JDK1.0时就已存在,我们认为任何一个类只要继承了Thread类就可以被成为一个线程类。
  • Thread类有一个 public void run()方法,子类必须重写这个方法,方法体里写的是线程需要做的事。注意:这个方法没有返回值,表示只是用来消费,不能获取。
  • 可以直接调用Thread.run()方法,但是这样运行跟普通方法一样,并没有实现线程调度(线程交替执行)的效果。Thread类还有一个start()方法(调用此方法执行的方法体为run()方法中的代码块),这个方法才真正表示启动线程,交由CPU调度。

  • 疑问?为什么多线程启动不是调用run()而必须调用start() ?
  • 这个问题可以通过源码来解析(基于JDK1.8)
public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

    private native void start0();
复制代码
  • 通过观察Thread类中的start()源码中的实现,我们会看到前两行有个判断然后抛出个 IllegalThreadStateException 这是个运行时的异常,属于RuntimeException的子类。这个异常是用来判断同一个线程是否多次执行,如果多次执行则抛出这个异常。所以我们可以根据源码来断定一个线程如果重复调用是不合理的,会抛出异常。
  • 在代码块的最下方还有一个 start0()方法,Thread的start()方法中调用了这个方法。我们初学者看到这个方法可能会有点奇怪,感觉跟抽象方法有点像,但是这个native是个啥玩意。没见过这种修饰符呀。
  • Java开发里有一种称为JNI的技术,全称为 Java Native Interface,这个技术是调用本机的操作系统提供的接口。我们知道线程需要CPU的调度进行资源分配才能运行,所以此操作是JVM根据不同的操作系统做特定的实现,这样就不用改代码了。

总结:

因为直接调用run()方法就跟调用普通方法一样,没有线程的概念。调用start()方法的话不仅仅调用run()方法的执行代码,还会根据不同的操作系统CPU的调度进行资源分配。
复制代码

实现Runnable接口

虽然Thread类可以直接实现多线程了,但是我们知道Java中有一个单继承的问题,所以我们对于类的继承都应该尽量回避。那么对于多线程也一样,为了解决单继承的限制,而有了Runnable接口。
复制代码

  • Runnable接口上加了一个注解,表明为函数式接口(只有一个抽象方法,JDK1.8后可以增加默认方法)。
  • 我们只需要实现Runnable接口并重写里面的run()方法就可以(跟继承Thread类一样,代码没有变动)。

但是我们怎么启动实现了Runnable接口的线程类呢?前面的Thread类有start()方法启动,但是现在是个接口呀。

  • 首先,我们需要记住不管在何种情况下,如果想启动线程一定要依靠Thread类来完成。

我们看下Thread类的构造函数:

  • 我们发现Thread类的构造器可以传入一个Runnable接口,那么根据Java的多态性,Runnable的实现类肯定也可以传入。

  • 这样我们就可以启动多线程了。

总结:

实现Runnable接口就避免了单继承局限,所以在日常工作中,我们一般都采用实现Runnable接口的方式来实现多线程。
复制代码

两种多线程实现方式的区别?(面试题)

  • 首先,使用Runnable接口解决了单继承的局限。至少在这一点上就已经下了死定义 ----------如果要使用多线程,就采用实现Runnable接口的方案。
  • 我们先看下Thread类源码中的定义:

我们发现Thread类居然实现了Runnable接口,这就有点搞笑了。那么当下的结构就成了这样:

Thread类和我们自定义的线程类都实现了Runnable接口,但是自定义的线程类需要靠Thread来调用。 从操作上来说,客户端调用Thread.start()方法,实际上是Thread调用了我们自定义线程类的run()方法。 这样整个的结构看起来就非常像代理设计模式,但是如果是代理设计模式,客户端也应该调用的是Runnbale接口中的方法,那也应该是run()方法才对。 产生这样的结构其实是最初设计时想到了代理的结构但是没有完全做成代理模式的样子。所以保留成了这样子。 除了以上的联系之外,使用Runnable接口可以比Thread类能够更好的描述出数据共享(多个线程访问同一个资源)的概念。 范例1:

这段代码运行后发现每个线程都各自卖各自的票,此时并不存在数据共享概念。 我们可以理解为3个人轮流买票,总共有10张票。而不是每个人各买10张票。 (备注:并不是说Thread做不到数据共享,我们完全可以把范例2中实现Runnable的自定义线程类改成继承Thread类,但是这样看好像有点不伦不类。因为继承Thread类本来就有start()方法了,还需要创建别的Thread对象来启动线程。。所以采用范例2来实现数据共享更合乎常理)

范例2:

我们修改了一些代码,自定义线程类实现了Runnable接口,然后创建3个Thread对象共同对自定义线程类操作。

这样的话3个Thread类都指向了自定义线程类的对象。

总结:

  • Thread类时Runnable接口的实现类,使用Runnable接口实现多线程可以避免单继承局限
  • 实现Runnable接口的线程类可以比继承Thread类的线程类更加清楚的描述数据共享这一概念。

这篇先介绍在这里,从中我们可以详细的了解了多线程的两种实现方式,但是这两种方式中的run()方法都属于消费器,没有返回值。如果我们希望线程执行完能给我们一个返回呢?这就用到了JDK1.5新出的Callable接口。好的,我们下篇再介绍如何使用Callable接口实现多线程并有返回值以及一些线程中常用的方法。大家喜欢我的博客的话请关注点赞分享呀,如有哪些地方说的不对的,也欢迎在评论区一起探讨探讨。See you~~

转载于:https://juejin.im/post/5cbf33fe6fb9a032036186e2

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值