作者 | ithuangqing

来源 | 编码之外(ID:ithuangqing)

碰见个奇怪的多线程问题

小白们也不用怕,今天的文章你们都能看得懂,最近的学习中,碰到这样的一个问题:

Java创建多线程的方式有哪几种啊?

你可能会说啦,这还不简单,不就是:

  1. 继承Thread类
  2. 实现Runnable接口

好像也是,如果你让我回答这个问题,我似乎也会这样回答,顶多我会再回答一个callable的方式,但是啊,最近看到这样的一个说法,让我陷入了深深的思考啊

Java中创建多线程的方法有且仅有一种,那就是new Thread的方式

嗯哼?这是怎么回事呢?这个就有点颠覆认知啊,我有点不敢相信了,那么这到底是怎么回事呢?看到这个回答我觉得我应该深入探讨下这个问题。

一般这问题都是怎么问的

关于上述说到的这个问题啊,并不是什么高深的问题,而且我们大多数人都能够回答上来,只不过可能回答的不全面,我这里带着大家去找找这个问题在面试题中是怎么出现的。

首先随便搜到了一套关于Java多线程的面试题,其中找到了关于本题的这种问法:

你可以思考下,这个问题让你回答,你会怎么回答,它说的四种,有哪四种?

这里我希望大家的着眼点应该是它怎么问的,它这里说的是线程的实现方式,记住,是实现方式,我们继续找找其他面试题:

在这个版本中关于这个问题是这样问的,注意是创建线程,我们上面那一个说的是实现线程,是的,就是不同的说法,但是是一样的嘛?

如果我们在面试中被问到这样的问题,无论是问我们创建线程的方式还是实现线程的方式,我们的答案几乎一定是围绕着继承Thread类和实现Runnable接口这几个去说的,我相信应该不会有多少人上去就说:

创建线程的方式有且只有一种,那就是new Thread的方式

估计你这样的问答一定会被反问,为什么啊?是啊,为什么啊,其实看到这个回答,在我认真思考了之后我觉得这个说法是没有啥错误的

难道我之前学的都是错的

我们一起来分析一下,在Java中啊,有这么个段子,就是没有女朋友的咋办,那就new一个啊,学习Java的都知道这是怎么回事,在Java中万物皆对象啊,创建对象一般就是new的方式了。

在Java中,Thread这个是线程类,按理说我们创建一个线程对象,那就应该是new Tread的方式啊,我们先来看我们平常都是怎么去创建一个线程的,一般的我们推荐实现接口的方式,这是源于Java的单继承多实现,我们来一起看下代码:

class MyThread implements Runnable{@Overridepublic void run(){System.out.println("实现Runnbale的方式……");}}

当我们写完上述代码之后,我们就需要停下来思考以下了,这里我们创建了一个线程了嘛?我们这里貌似只是创建了一个实现了Runnbale接口的类,好像并没有哪里有体现我们创建线程了啊,我们来做个简单的测试:

public class Test {    public static void main(String[] args) {        //获取线程数        ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();        while(threadGroup.getParent() != null){            threadGroup = threadGroup.getParent();        }        int totalThread = threadGroup.activeCount();        System.out.println("当前线程数:"+totalThread);    }}

我这里写了一段简单的程序,就是获取当前默认线程组中有几个线程,这段代码你不用去管他,只需要之道它有什么用,我们运行试一下:

这里是6,然后我们加上我们之前实现Runnbale那个类,一起来看下:

public class Test {    public static void main(String[] args) {        //获取线程数        ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();        while(threadGroup.getParent() != null){            threadGroup = threadGroup.getParent();        }        int totalThread = threadGroup.activeCount();        System.out.println("当前线程数:"+totalThread);    }}class MyThread implements Runnable{    @Override    public void run(){        System.out.println("实现Runnbale的方式……");    }}

在main方法中并没有关于MyThread的体现,可想,目前线程数还是6,我们一般都是怎么使用这个MyThread的呢?是不是这样?

public class Test {    public static void main(String[] args) {                Thread thread = new Thread(new MyThread());        thread.start();        //获取线程数        ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();        while(threadGroup.getParent() != null){            threadGroup = threadGroup.getParent();        }        int totalThread = threadGroup.activeCount();        System.out.println("当前线程数:"+totalThread);    }}

熟悉吧,我们一般都是这样操作的,这里想必大家也都知道,需要调用start才是真正的启用线程,我们再来运行下看看:

看吧,线程数增加了1,也打印出相关数据了,这才创建了一个线程,原因是我们写了这么些代码:

Thread thread = new Thread(new MyThread());        thread.start();

发现什么没,重点来了,就是这里的new Thread,我们接下来看看这样的代码:

public class Test {    public static void main(String[] args) {        new Thread();        //获取线程数        ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();        while(threadGroup.getParent() != null){            threadGroup = threadGroup.getParent();        }        int totalThread = threadGroup.activeCount();        System.out.println("当前线程数:"+totalThread);    }}

猜一下,现在的线程数是多少?

会不会有人说是7,知道为什么嘛,那是因为你没有调用start的啊,再来看:

public class Test {    public static void main(String[] args) {        new Thread().start();1        //获取线程数        ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();        while(threadGroup.getParent() != null){            threadGroup = threadGroup.getParent();        }        int totalThread = threadGroup.activeCount();        System.out.println("当前线程数:"+totalThread);    }}

这属于线程的基础知识了,题外话,你可知道为啥调用start而不是run嘛?

以上说明一个什么问题呢?真正的创建线程还是通过new Thread啊,然后调用start启动该线程,你看这个:

Thread thread = new Thread(new MyThread());        thread.start();

也是new Thread的方式,然后构造函数传入一个Runnbale,我们看看Thread的构造函数吧:

看到了吧,这里可以传入一个Runnable,我们继续往下思考。

创建线程干嘛

你想一下,我们创建线程干嘛,简单来说,是不是也是需要这个线程为我们干活啊,怎么干活嘞,简单来说是不是就是这个run方法啊:

@Override    public void run(){        System.out.println("实现Runnbale的方式……");    }

我们在这个run方法中去执行一些任务,其实在Thread类中也有这个run方法,可以看一下:

@Override    public void run() {        if (target != null) {            target.run();        }    }

Thread类中的run方法没有具体的执行某些任务,而是去执行target中的run,这个target是啥:

private Runnable target;

是个Runnbale,你再看看我们实现Runnbale的MyThread的类:

class MyThread implements Runnable{    @Override    public void run(){        System.out.println("实现Runnbale的方式……");    }}

然后再看这个:

Thread thread = new Thread(new MyThread());        thread.start();

我想你应该明白了吧,这么一大圈就是为了去执行MyThread中的run方法,因为这是我们新建的这个线程要干的活啊。

可能我们以前真的错了

我们再看看长说的另一个方式,那就是继承Thread类的形式:

class A extends Thread {    @Override    public void run() {        System.out.println("继承Thread类的线程……");    }}

这个我们知道Thread类中有这个run方法并且上面也带大家看了,所以这里就是重写了run方法,而如果我们要启动这个线程则要这样:

new A().start();

这里的new A本质还是new Thread啊,不用解释吧,然后我们再看其他的方式,比如匿名内部类的方式:

new Thread(new Runnable() {            @Override            public void run() {                System.out.println("匿名内部类的方式创建线程");            }        }).start();

多么明显,还是new Thread啊,再继续看看实现callable的方式:

class C implements Callable {    @Override    public Integer call() throws Exception {        System.out.println("实现callable的形式创建的线程");        return 1024;    }}

然后我们还需要这样:

FutureTask futureTask = new FutureTask<>(new C());        Thread thread = new Thread(futureTask);        thread.start();        System.out.println(futureTask.get());

这里真的创建线程还是new Thread的方式。

所以经过上述的简单分析啊,我们之前的理解可能真的错了,我们经常说,创建线程的方式有什么继承Thread类的方式,还可以实现Runnable接口等等,但是现在看来,这似乎是错误的,正确的回答应该是:

创建线程的方式有且仅有一种,那就是new Thread()的方式

盘点之前的错误回答

说到这里我觉得有必要盘点一下我们之前的错误回答了,因为很多人即使按照之前的回答,要么回答的不全整,要么回答的不够好,首先,我们看看在之前我们最完整的回答应该包含以下几种方式:

  1. 继承Thread类
  2. 实现Runnable接口
  3. 匿名内部类
  4. 实现callable接口
  5. 使用线程池

以上五个回答是比较完整的了,一般啊,我们推荐实现接口的方式,这是源于java的单继承和多实现,另外实现callable和使用线程池在实际中应用的更多。

那么有些人可能会有疑惑了,你既然你说创建线程的方式有且仅有一种那就是new Thread的方式,那么上述这五种是干嘛的啊。

总结

是啊,那我们之前脱口而出的这些又是干嘛的呢?经过我们上面的分析,我想大家应该有看到,无论是继承Thread类还是实现Runnbale,又或者其实其他方式,好像目的就是为了去实现那个run方法(callable的不是),准确来说就是去执行我们真正要做的任务,也就是执行任务,也就是说啊,我们创建线程只有一种方式那就是new Thread的方式

但是你想啊,我们创建线程是让他干活的,那干啥活嘞,我们可以通过继承Thread类,然后重写run方法告诉线程该干嘛,又或者我们整一个Runnable,然后实现其中的run方法,然后把这个Runnable扔给Thread,告诉线程该干嘛,其他的也是同样的道理。

那么我们是不是可以理解为:

这些都是线程执行任务的方式,或者说是真正实现线程任务的方式,但是无论怎样,说是创建线程的方式,是不是有点不对呢?

那么,你们是如何看呢?欢迎留言交流!

java多线程为啥一直用的一个线程_一个Java多线程的问题,颠覆了我多年的认知!...相关推荐

  1. 一个http请求就是一个线程吗,java的服务是每收到一个请求就新开一个线程来处理吗...

    一个http请求就是一个线程吗,java的服务是每收到一个请求就新开一个线程来处理吗 答案是:是,一个http请求,就是一个线程. https://blog.csdn.net/elvis_lfc/ar ...

  2. java 线程_理解java多线程

    树枝头玩魔法班卓琴弦乐器的生物与发光的蝴蝶,数字艺术风格,插画绘画creature with java多线程 1.实现Runnable接口相比于继承Thread类: (1)适合多个相同的程序代码的线程 ...

  3. java文件对比7,一个线程读一个线程写、返回给前端进度条数据

    java文件对比 controller Service Serviceimpl 读取文件多线程工具类 对比文件多线程工具类 控制台结果 返回结果 进度条结果 个人总结 这个其实写的是有点问题的,想的是 ...

  4. java开启一个线程_【jdk源码分析】java多线程开启的三种方式

    1.继承Thread类,新建一个当前类对象,并且运行其start()方法 1 packagecom.xiaostudy.thread;2 3 /** 4 * @desc 第一种开启线程的方式5 *@a ...

  5. java 1000个线程_关于Java多线程的一个问题

    声明要同步的方法时,例如: public synchronized void foo() { // Do something } 编译器将它看作是你写的: public void foo() { sy ...

  6. java 事件分发线程_深入浅出Java多线程(2)-Swing中的EDT(事件分发线程) [转载]...

    本系列文章导航 本文主要解决的问题是: 如何使其Swing程序只能运行一个实例? 抛开Swing, 我们的程序是通过java 命令行启动一个进程来执行的,该问题也就是说要保证这个进程的唯一性,当然如果 ...

  7. java如何关闭一个线程_如何关闭一个java线程池

    Java 并发工具包中 java.util.concurrent.ExecutorService 接口定义了线程池任务提交.获取线程池状态.线程池停止的方法等. JDK 1.8 中,线程池的停止一般使 ...

  8. 多线程的实现方式_Java中线程的状态及多线程的实现方式

    线程的状态 线程状态图: 说明: 线程共包括以下5种状态.1. 新建状态(New) : 线程对象被创建后,就进入了新建状态.例如,Thread thread = new Thread().2. 就绪状 ...

  9. 【Java_多线程并发编程】基础篇—线程状态及实现多线程的两种方式

    1.Java多线程的概念 同一时间段内,位于同一处理器上多个已开启但未执行完毕的线程叫做多线程.他们通过轮寻获得CPU处理时间,从而在宏观上构成一种同时在执行的假象,实质上在任意时刻只有一个线程获得C ...

最新文章

  1. SAP一句话入门:Human Resource
  2. 谷歌对用户搜索加密这一做法对seo的影响!
  3. php给html传值,PHP传值到不同页面的三种常见方式及php和html之间传值问题_PHP
  4. html+css 小案例(一)
  5. [国嵌攻略][038][时钟初始化]
  6. windows 下使用 virtualenv 创建虚拟环境
  7. 前端加密使用AES与后端配合(ECB)
  8. Yaml读写--SnakeYaml
  9. Vue 实现下载本地静态文件
  10. Android日历阴阳历转换的实现(包括日期选择器)
  11. 攻防世界之互相伤害!!!
  12. JMeter_Ubuntu上安装jmeter
  13. lseek和文件末尾
  14. Gitlab----Pipline流水线语法only、except、rules、workflow
  15. mysql int 可以是负数吗_int型包括负数吗
  16. 爱迪尔 门锁接口文档_门锁接口说明
  17. 网易2017秋招编程题集合
  18. Contextual Transformer Networks for Visual Recognition论文以及代码解析
  19. 需抢购的加密兔成小米最大败笔,学习网易星球聚拢用户才是正途
  20. C++ 的fcntl函数

热门文章

  1. [转] jQuery 选择器
  2. 微软Silverlight==跨浏览器、跨客户平台的技术
  3. .NET Remoting Security使用小结 – TcpChannel
  4. Scrapy_CSS选择器
  5. OpenCV实战4: HOG+SVM实现行人检测
  6. LeetCode-1两数之和
  7. 一张图图片分块html,css img图片是内联还是块?
  8. uefi linux开发环境,开发者为 Linux 添加了一系列 RISC-V UEFI 支持补丁
  9. 延时函数、数码管显示头文件(单片机)
  10. java中如何声明班干部,java类成员变量的定义和声明