乐趣区

关于java:面试官问我创建线程有几种方式我笑了

前言

多线程在面试中基本上曾经是必问项了,面试官通常会从简略的问题开始提问,而后再一步一步的开掘你的知识面。

比方,从线程是什么开始,线程和过程的区别,创立线程有几种形式,线程有几种状态,等等。

接下来天然就会引出线程池,Lock,Synchronized,JUC 的各种并发包。而后就会引出 AQS、CAS、JMM、JVM 等偏底层原理,一环扣一环。

这一节咱们不聊其余的,只说创立线程有几种形式。

是不是感觉非常简单,不就是那个啥啥那几种么。

其实不然,只有咱们给面试官解释分明了,并加上咱们本人的了解,能力在面试中加分。

注释

一般来说咱们比拟罕用的有以下四种形式,上面先介绍它们的应用办法。而后,再说面试中怎么答复面试官的问题比拟适合。

1、继承 Thread 类

通过继承 Thread 类,并重写它的 run 办法,咱们就能够创立一个线程。

  • 首先定义一个类来继承 Thread 类,重写 run 办法。
  • 而后创立这个子类对象,并调用 start 办法启动线程。

2、实现 Runnable 接口

通过实现 Runnable,并实现 run 办法,也能够创立一个线程。

  • 首先定义一个类实现 Runnable 接口,并实现 run 办法。
  • 而后创立 Runnable 实现类对象,并把它作为 target 传入 Thread 的构造函数中
  • 最初调用 start 办法启动线程。

3、实现 Callable 接口,并联合 Future 实现

  • 首先定义一个 Callable 的实现类,并实现 call 办法。call 办法是带返回值的。
  • 而后通过 FutureTask 的构造方法,把这个 Callable 实现类传进去。
  • 把 FutureTask 作为 Thread 类的 target,创立 Thread 线程对象。
  • 通过 FutureTask 的 get 办法获取线程的执行后果。

4、通过线程池创立线程

此处用 JDK 自带的 Executors 来创立线程池对象。

  • 首先,定一个 Runnable 的实现类,重写 run 办法。
  • 而后创立一个领有固定线程数的线程池。
  • 最初通过 ExecutorService 对象的 execute 办法传入线程对象。

到底有几种创立线程的形式?

那么问题来了,我这里举例了四种创立线程的形式,是不是阐明就是四种呢?

咱们先看下 JDK 源码中对 Thread 类的一段解释,如下图。

There are two ways to create a new thread of execution

翻译:有两种形式能够创立一个新的执行线程

这里说的两种形式就对应咱们介绍的前两种形式。

然而,咱们会发现这两种形式,最终都会调用 Thread.start 办法,而 start 办法最终会调用 run 办法。

不同的是,在实现 Runnable 接口的形式中,调用的是 Thread 本类的 run 办法。咱们看下它的源码,

这种形式,会把创立的 Runnable 实现类对象赋值给 target,并运行 target 的 run 办法。

再看继承 Thread 类的形式,咱们同样须要调用 Thread 的 start 办法来启动线程。因为子类重写了 Thread 类的 run 办法,因而最终执行的是这个子类的 run 办法。

所以,咱们也能够这样说。在实质上,创立线程只有一种形式,就是结构一个 Thread 类(其子类其实也能够认为是一个 Thread 类)。

而结构 Thread 类又有两种形式,一种是继承 Thread 类,一种是实现 Runnable 接口。其最终都会创立 Thread 类(或其子类)的对象。

再来看实现 Callable,联合 Future 和 FutureTask 的形式。能够发现,其最终也是通过 new Thread(task) 的形式结构 Thread 类。

最初,在线程池中,咱们其实是把创立和治理线程的工作都交给了线程池。而创立线程是通过线程工厂类 DefaultThreadFactory 来创立的(也能够自定义工厂类)。咱们看下这个工厂类的具体实现。

它会给线程设置一些默认值,如线程名称,线程的优先级,线程组,是否是守护线程等。最初还是通过 new Thread() 的形式来创立线程的。

因而,综上所述。在答复这个问题的时候,咱们能够说实质上创立线程就只有一种形式,就是结构一个 Thread 类 。(此论断借鉴来源于 Java 并发编程 78 讲 — 徐隆曦)

集体想法

然而,在这里我想对这个论断略微提出一些疑难(若有不同见解,文末可留言交换~)。。。

集体认为,如果你要说有 1 种、2 种、3 种、4 种 其实也是能够的。重要的是,你要能说出你的根据,讲出它们各自的不同点和共同点。讲得有条有理,让面试官对你频频点头。。

说只有结构 Thread 类这一种创立线程形式,集体认为还是有些牵强。因为,无论你从任何伎俩登程,想创立一个线程的话,最终必定都是结构 Thread 类。(包含以上几种形式,甚至通过反射,最终不也是 newInstance 么)。

那么,如果依照这个逻辑的话,我就能够说,不论创立任何的对象(Object),都是只有一种形式,即结构这个对象(Object)类。这个论断仿佛有些太过无聊了,因为这是一句十分正确的废话。

以 ArrayList 为例,我问你创立 ArrayList 有几种形式。你八成会为了夸耀本人晓得的多,跟我说,

  1. 通过构造方法,List list = new ArrayList();
  2. 通过 Arrays.asList("a", "b");
  3. 通过 Java8 提供的 Stream API,如 List list = Stream.of("a", "b").collect(Collectors.toList());
  4. 通过 guava 第三方 jar 包,List list3 = Lists.newArrayList("a", "b");

等等,仅以上就列举了四种。当初,我通知你创立 ArrayList 就只有一种形式,即结构一个 ArrayList 类,你抓狂不。

这就如同,我问你从北京登程到上海去有几种形式。

你说能够坐汽车、火车、坐动车、坐高铁,坐飞机。

那不对啊,动车和高铁都属于火车啊,汽车和火车都属于车,车和飞机都属于交通工具。这样就是只有一种形式了,即坐交通工具。

这也不对啊,我不坐交通工具也行啊,我走路过来不行么(我插眼传送也能够啊,就你皮~)。

最初论断就是,只有一种形式,那就是你人到上海即可。这这这,这算什么论断。。。

所以集体认为,说创立线程只有一种形式有些欠妥。

好好的一个技术文,差一点被我写成议论文了。。。

这个仁者见仁智者见智吧。

最初,咱们看一下我从网上看到的一个十分有意思的题目。

然而,咱们晓得 HashMap 自身就有四个不同参数的构造函数,如下图,

有四种形式能够创立 HashMap 对象,除此之外,还能够通过

乏味的题目

问:一个类实现了 Runnable 接口就会执行默认的 run 办法,而后判断 target 不为空,最初执行在 Runnable 接口中实现的 run 办法。而继承 Thread 类,就会执行重写后的 run 办法。那么,当初我既继承 Thread 类,又实现 Runnable 接口,如下程序,应该输入什么后果呢?

public class TestThread {public static void main(String[] args) {new Thread(()-> System.out.println("runnable")){
            @Override
            public void run() {System.out.println("Thread run");
            }
        }.start();}
}

可能乍一看很懵逼,这是什么操作。

其实,咱们拆解一下以上代码就会晓得,这是一个继承了 Thread 父类的子类对象,重写了父类的 run 办法。而后,父对象 Thread 中,在构造方法中传入了一个 Runnable 接口的实现类,实现了 run 办法。

当初执行了 start 办法,必然会先在子类中寻找 run 办法,找到了就会间接执行,不会执行父类的 run 办法了,因而后果为:Thread run。

若假如子类没有实现 run 办法,那么就会去父类中寻找 run 办法,而父类的 run 办法会判断是否有 Runnable 传过来(即判断 target 是否为空),当初 target 不为空,因而就会执行 target.run 办法,即打印后果:runnable。

所以,上边的代码看起来简单,实则很简略。透过景象看实质,咱们就会发现,它不过就是考查类的父子继承关系,子类重写了父类的办法就会优先执行子类重写的办法。

和线程联合起来,如果对线程运行机制不相熟的,很可能就会被蛊惑。

退出移动版