Java面试之多线程:Java创建多线程为什么只有一种方式?

Posted fntp

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java面试之多线程:Java创建多线程为什么只有一种方式?相关的知识,希望对你有一定的参考价值。


关于线程的创建,其实我一直都有话说,于是今天,我来开个头。今天依旧一样,长话短说,只谈创建多线程。你好,我是fntp!今天要跟大家分享的是博主最近面试Java实习所遇到的一些问题!那就是经常性被问到的多线程!今天是多线程的第一个核心,也是较为轻松的一个话题,我们先来讨论一下,Java实现多线程,究竟有多少种方法。

在Java中实现线程的方式有几种呢?两种?三种?四种?五种?甚至更多?其实都不对。说两种的同学有可能还在JavaSE的苦海中挣扎,忘不了继承Thread类与实现Runnable接口的情感纷争,说是三种的同学,学的还算可以,还没忘记Callable接口与Future,JDK1.5之后的新的API也就是Future,说是四种的兄弟可能说还有线程池ThreadPool的方式,也可能还会有人说,还有TimerThread等等,而我想告诉大家的是,新建线程的方式…其实我跟你讲,你不要告诉别人,其实Java创建线程只有一种方式!没错,只有一种

我们都知道,Java是一门支持多线程的性能优良的语言,并且Java 语言为开发者提供了并发机制,允许开发人员在程序中创建并执行多个线程,每个线程可完成一个功能一个功能可实现一个或多个任务,并与其他线程并发执行。我们说,这种机制就是多线程。

创建多线程的方式看似有很多种,继承Thread类,实现Runnable接口,实现Callable接口,调用ExcutorService任务解释器装载进入线程池等等,在JDK中又实现了对Thread的新的封装引用,又出现新的创建多线程的方式,比如TimerThread定时任务线程等。

乍一看确实创建线程的方式有多种多样,但是本质上,只有一种,那就是构造Thread类。我知道这种回答,可能会让众多CSDN的C友直呼震惊啊!

所谓构造Thread类就是多态的理解,父类类型的对象指向子类类型的引用。说大白话就是继承Thread类,基于此种方式实现多线程。我相信你肯定会说凭什么,凭什么只有这一种方式去实现多线程。我们一起来看看吧。

首先,你得必须知道线程的六大生命周期!

(1)NEW状态。

也称之为 新建状态,很简单理解,就是你New了一个Thread的子类,或者是Runnable的实现类。

(2)Runnable状态。

这是在调用Start()方法之后的Runnable状态,你翻译一下,Runnable,是一个形容词,看看人家用词描述的严谨程度,描述状态名词使用形容词,也就是可运行的,注意是可运行的,运行了吗?没有!是说你有运行的资格,但是并没有运行!所以不是运行状态!

(3)BLOCKED阻塞状态。

BLOCKED是指受阻塞并且正在等待监视器锁的某一线程的线程状态。什么情况下会触发这个状态?最直接的IO操作,计算机中人机交互永远都离不开Input与Output,IO操作的时候,大概率会触发阻塞状态。最常见就是qq与微信同时启动,再开一个谷歌浏览器,这个时候你Win+E你看看你电脑卡不卡就完了,在代码中去理解就是,代码执行,阻塞发生,在该操作return之前,线程不会继续此线程下面的代码。或者是你调用了Sleep()方法,这个是比较常见的一种阻塞。好了,这就是阻塞。

(4)WAITING状态。

这个也有一些比较常见的场景,比如,线程因为调用了Object.wait()或Thread.join()而未运行,就会进入WAITING状态。

(5)TIMED_WAITING状态。

具有指定等待时间的某一等待线程的线程状态。

(6)TERMINATED状态。

线程被终止了或者是,注意,是或者是线程终止了。也就是终止状态。终止状态对应两种不同的情况,一种是自动终止,这是由操作系统所分配的时间片决定的,一种是强制终止,这是用户决定的。至于先终止哪一个需要根据时间片轮转定义以及优先级决定。

好了,知道这些基础知识后,我们就可以来分析一下,为什么Java创建多线程只有一种方式了。我们先看实现Runnable接口:

package com.sinsy.fntp.thread;
public class ThreadTest 
    /**
     * fntp
     * @param args
     */
    public static void main(String[] args) 
        //创建第一个线程
        Thread thread01_main = new Thread (new RunnableImplTest ());
        System.out.println ("执行thread01");
        thread01_main.start ();
        //创建第二个线程
        Thread thread02_main = new Thread ();
        System.out.println ("执行thread02线程");
        thread02_main.start ();
    


class RunnableImplTest implements Runnable
    @Override
    public void run() 
        //实现Runnable接口必须也要重写方法 重写是指覆盖原方法
        System.out.println ("我执行啦!");
        //当前正在执行的是哪个Java类?查一下
        asr ();
    
    public void asr()
        System.out.println (Thread.currentThread ().getStackTrace ()[0].getClassName ());
    

首先我们说,第一种最常见的就是实现Runnable接口, 新建一个Java类实现 Runnable 接口,然后重写该接口的 run() 方法,之后只需要把这个实现了 接口并重写了run() 方法的接口实现类的实例,也就是实现类的对象,作为方法参数传到 Thread 类中就完成了实现多线程的目的。我们先留意一点,实现Runnable,其核心在于哪里?在于我们可以将这个实现类的实例作为方法参数传递到Thread类中来执行。那么,我想问你的是,它本身是一个线程吗?不是吧,对吧,如果接口实现类本身是一个线程,我们前面刚刚介绍的,启动线程直接调用start方法不就可以了吗,你看看Runnable接口实现类有这个方法吗?

答案是肯定没有的,因为Runnable中没有这个方法,这个方法是来自于Thread类才独有的。

所以我们很容易就能想到,执行Run方法是Thread去执行的,那么,好接下来第二个问题,我们将接口实现类传递进入之后干什么用呢?我们不妨先看这张图:发现端倪了吗?不假!Thread是实现了Runnable接口,好家伙,人家Runnable的实现类对象实例都传递给你做老婆了,哦不,是传递给你做参数了,你还不知足,还包‘二奶’?真的是Thread包二奶吗?我们继续往下看:

既然你也实现了人家Runnable接口,你也得对人家负责啊,所以你也得重写run方法吧,否则你就是一个渣男,那么,Thread是不是脚踩两只船呢?包二奶了吗?咳咳预知后事如何,且看…我呸,且看如下代码:

我们发现哈,原来啊!Thread没有不负责,他切实有实现他的run方法哈,那么target是什么鬼呢?我们接着看:

哦,target是他的成员变量,是Runnable类型的对象,接着看:

这块还有一个方法,用来设置Thread类的参数,这块也有target,哈哈,离真相不远了…

哎呀,真相大白了,target在这里进行赋值,也就是说,Thread将传递来的参数,这个参数是通过new产生的,必然会在堆区中生成地址,Thread通过这个构造方法,将堆区中的地址引过来赋值给了本地成员属性,因此啊!Thread没有脚踩两只船,更没有包二奶,而是直接将传递来的参数接口实现类对象直接拿来,让他去调用Run方法,嗦嘎,这不就是妙蛙种子吃了妙脆角进了米奇妙妙屋,妙到家了吗?明白了吧,好的,原来,实现Runnable接口只是为了两个目的,第一,将实现了接口的实现类的对象作为参数传递到thread类中,然后呢,构造Thread对象,然后通过thread类的start方法去启动多线程!好家伙,整半天,实现Runnable接口就是为了告诉Thread应该做什么,而启动Thread还得依靠Thread本身!毕竟啊,start方法只有Thread有啊!好家伙,现实版做自己的贵人啊!

那么:线程池呢?

关于线程池,我们都知道,对于线程池而言,本质上是通过线程工厂创建线程的,默认采用 DefaultThreadFactory ,这个Java类在执行构造方法的时候,会给线程参数传递一些线程的默认参数,比如:Thread_Name线程的名字、设置为守护线程与否,以及设置初始化线程的优先级等。但是无论怎么设置这些属性,最终它还是通过 new Thread() 创建线程的。这也就是为什么说,线程池同样,也是属于一种创建线程的方式之一,因为其底层到最后都是通过new Thread类的方式来构建线程对象,从而来创建,启动和操作线程的。

最后,我们来简单说一下Callable吧,其实不说你也知道了,你肯定清楚,通过Callable 创建线程,与Runnable相比,Runnable是无返回值的,而 Callable 和与之相关的 Future、FutureTask是有返回值的,它们可以把线程执行的结果作为返回值返回。这也是他们之间的一些明显的区别。

然并卵,无论是 Callable 还是 FutureTask,它们首先和 Runnable 一样,都是一个任务,你会发现,在ExcutorService中,依旧可以将Runnable实现类作为参数传递进入执行,这也就印证了Runnable与Callable都是需要被执行的任务而已,它们本身就不是线程。但是可以将它们放到线程池中去执行,可以通过 submit() 方法把任务放到线程池中,并由线程池创建线程,不管用什么方法,最终都是靠线程来执行的,而线程的创建方式仍脱离不了这一种方式,也就是继承 Thread 类传递任务参数,最终使用该类的start方法来启动多线程。所以,我想知道的是,你明白了吗?

以上是关于Java面试之多线程:Java创建多线程为什么只有一种方式?的主要内容,如果未能解决你的问题,请参考以下文章

java面试之多线程篇

java面试之多线程篇

Java面试之多线程

Java面试之多线程

Java面试准备之多线程

java面试之多线程