详细分析 Java 中实现多线程的方法有几种?(从本质上出发)

正确的说法(从本质上出发)

  • 实现多线程的官方正确方法: 2 种。

  • Oracle 官网的文档说明

  • 方法小结

    • 方法一: 实现 Runnable 接口。

    • 方法二: 继承 Thread 类。

  • 代码示例

    /**
    * <p>
    * 实现 Runnable 接口的方式创建线程
    * </p>
    *
    * @author 踏雪彡寻梅
    * @version 1.0
    * @date 2020/9/7 - 00:34
    * @since JDK1.8
    */
    public class RunnableStyle implements Runnable {
    @Override
    public void run() {
    System.out.println("用 Runnable 方式实现线程~~~");
    } public static void main(String[] args) {
    Thread thread = new Thread(new RunnableStyle());
    thread.start();
    }
    }
    /**
    * <p>
    * 继承 Thread 类的方式创建线程
    * </p>
    *
    * @author 踏雪彡寻梅
    * @version 1.0
    * @date 2020/9/7 - 00:37
    * @since JDK1.8
    */
    public class ThreadStyle extends Thread {
    @Override
    public void run() {
    System.out.println("用 Thread 方式实现线程~~~");
    } public static void main(String[] args) {
    new ThreadStyle().start();
    }
    }
  • 两种方式的对比

    • 方法一(实现 Runnable 接口)更好。

    • 方法二的缺点:

      1. 从代码的架构去考虑,具体执行的任务也就是 run 方法中的内容,它应该和线程的创建、运行的机制也就是 Thread 类是解耦的。所以不应该把他们混为一谈。从解耦的角度,方法一更好。

      2. 该方法每次如果想新建一个任务,只能去新建一个独立的线程,而新建一个独立的线程这样的损耗是比较大的,它需要去创建、然后执行,执行完了还要销毁;而如果使用 Runnable 接口的方式,我们就可以利用线程池之类的工具,利用这些工具就可以大大减小这些创建线程、销毁线程所带来的损耗。所以方法一相比于方法二的这一点,好在资源的节约上。

      3. 继承了 Thread 类之后,由于 Java 不支持双继承,那么这个类就无法继承其他的类了,这大大限制了我们的可扩展性。

  • 两种方式的本质区别

    • 方法一: 最终调用 target.run; ,通过以下两图可以知道使用这个方法时实际上是传递了一个 target 对象,执行了这个对象的 run 方法。

    • 方法二: run() 整个都被重写。一旦子类重写了父类的方法,原有方法会被覆盖被抛弃,即以下代码不会被这次调用所采纳。

    • 综上,两种方法都是执行了 run 方法,只不过 run 方法的来源不同。

  • 同时使用两种方法会怎样?

    • 代码演示

      /**
      * <p>
      * 同时使用 Runnable 和 Thread 两种实现线程的方式
      * </p>
      *
      * @author 踏雪彡寻梅
      * @version 1.0
      * @date 2020/9/7 - 22:38
      * @since JDK1.8
      */
      @SuppressWarnings("all")
      public class BothRunnableThread {
      public static void main(String[] args) {
      new Thread(new Runnable() {
      @Override
      public void run() {
      System.out.println("我来自 Runnable。。。");
      }
      }) {
      @Override
      public void run() {
      System.out.println("我来自 Thread。。。");
      }
      }.start();
      }
      }
    • 运行结果

    • 分析

      1. 首先创建了一个匿名内部类 Thread。传入了一个 Runnable 对象。

      2. 然后重写了 Thread 的 run 方法。最后启动线程。

      3. 因为重写了 Thread 的 run 方法,所以它父类的 run 方法就被覆盖掉了,所以即便传入了 Runnable 对象也不会执行它。

  • 总结

    • 从以上的分析中,准确的讲创建线程只有一种方式那就是构造 Thread 类,而实现线程的执行单元有两种方式(run 方法的两种不同实现情况)。

      • 方法一: 实现 Runnable 接口的 run 方法,并把 Runnable 实例传给 Thread 类,再让 Thread 类去执行这个 run 方法。

      • 方法二: 重写 Thread 的 run 方法(继承 Thread 类)。

经典错误说法(从本质上出发)

  1. ”线程池创建线程也算是一种新建线程的方式。“

    • 线程池创建线程代码示例

      import java.util.concurrent.ExecutorService;
      import java.util.concurrent.Executors; /**
      * <p>
      * 线程池创建线程的方法
      * </p>
      *
      * @author 踏雪彡寻梅
      * @version 1.0
      * @date 2020/9/7 - 23:05
      * @since JDK1.8
      */
      public class ThreadPools {
      public static void main(String[] args) {
      ExecutorService executorService = Executors.newCachedThreadPool();
      for (int i = 0; i < 1000; i++) {
      // 添加任务
      executorService.submit(new Task() {});
      }
      }
      } class Task implements Runnable {
      @Override
      public void run() {
      try {
      Thread.sleep(500);
      } catch (Exception e) {
      e.printStackTrace();
      }
      System.out.println(Thread.currentThread().getName());
      }
      }
    • 线程池创建线程源码(DefaultThreadFactory 中)

    • 分析

      • 通过线程池中的源码,可以知道线程池创建线程的方式本质上也是通过构造 Thread 的方式创建的。所以线程池创建线程的本质和上文中是一样的。所以不能简单的认为线程池也是一种新的创建线程的方式。
  2. ”通过 Callable 和 FutureTask 创建线程,也算是一种新建线程的方式。“

    • 类图展示

    • 分析

      • 从类图中可以知道这两个创建线程的本质也是和之前的一样的。
  3. ”无返回值是实现 Runnable 接口,有返回值是实现 Callable 接口,所以 Callable 是新的实现线程的方式。“

    • 和第 2 点差不多。
  4. 定时器是新的实现线程的方式。

    • 定时器实现线程代码示例

      import java.util.Timer;
      import java.util.TimerTask; /**
      * <p>
      * 定时器创建线程
      * </p>
      *
      * @author 踏雪彡寻梅
      * @version 1.0
      * @date 2020/9/7 - 23:48
      * @since JDK1.8
      */
      public class DemoTimmerTask {
      public static void main(String[] args) {
      Timer timer = new Timer();
      timer.scheduleAtFixedRate(new TimerTask() {
      @Override
      public void run() {
      System.out.println(Thread.currentThread().getName());
      }
      }, 1000, 1000);
      }
      }
    • 分析

      • 和前面几点一样,定时器创建线程的方法最终本质也离不开上文中的那两类方法。
  5. 匿名内部类和 Lambda 表达式的方式创建线程是新的创建线程方式。

    • 实际上也和前面几点一样是一个表面现象,本质上还是那两种方法。

    • 使用方式代码示例

      /**
      * <p>
      * 匿名内部类创建线程
      * </p>
      *
      * @author 踏雪彡寻梅
      * @version 1.0
      * @date 2020/9/7 - 23:54
      * @since JDK1.8
      */
      public class AnonymousInnerClassDemo {
      public static void main(String[] args) {
      // 第一种
      new Thread() {
      @Override
      public void run() {
      System.out.println(Thread.currentThread().getName());
      }
      }.start(); // 第二种
      new Thread(new Runnable() {
      @Override
      public void run() {
      System.out.println(Thread.currentThread().getName());
      }
      }).start();
      }
      }
      /**
      * <p>
      * Lambda 表达式创建线程
      * </p>
      *
      * @author 踏雪彡寻梅
      * @version 1.0
      * @date 2020/9/7 - 23:58
      * @since JDK1.8
      */
      public class LambdaDemo {
      public static void main(String[] args) {
      new Thread(() -> System.out.println(Thread.currentThread().getName())).start();
      }
      }
    • 运行结果

  • 总结

    • 多线程的实现方式,在代码中写法千变万化,但其本质万变不离其宗。

常见面试问题

  • 有多少种实现线程的方法?思路有 5 点。

    • 从不同的角度看,会有不同的答案。

    • 典型答案是两种。这两种方式的对比。

    • 从原理来看,两种本质都是一样的(都是实现 run 方法)。

    • 具体展开说其他方式(代码的实现上的不同方式,原理还是基于那两个本质)。

    • 将以上几点做结论。

  • 实现 Runnable 接口和继承 Thread 类哪种方式更好?

    • 从代码架构角度。(应该去解耦,两件事情:1.具体的任务即 run 方法中的内容;2.和线程生命周期相关的如创建线程、运行线程、销毁线程即 Thread 类去做的事情)

    • 新建线程的损耗的角度。(继承 Thread 类,需要新建线程、执行完之后还要销毁,实现 Runnable 接口的方式可以反复的利用同一个线程,比如线程池就是这么做的,用于线程生命周期的损耗就减少了)

    • Java 不支持多继承的角度。(对于扩展性而言)

详细分析 Java 中实现多线程的方法有几种?(从本质上出发)的更多相关文章

  1. java中实现多线程的方法有几种以及如何实现多线程

    多线程是java语言区别于其它语言的一大特点.其它语言向C++的话如果要实现多线程的话,必须调用操作系统的多线程机制(我不确定自己记得对不对 了感兴趣的自己上网查查看). 在java5以前实现多线程有 ...

  2. 详细分析 Java 中启动线程的正确和错误方式

    目录 启动线程的正确和错误方式 前文回顾 start 方法和 run 方法的比较 start 方法分析 start 方法的含义以及注意事项 start 方法源码分析 源码 源码中的流程 run 方法分 ...

  3. 【转】详细讲解Java中log4j的使用方法

    转载地址:http://www.233.com/Java/zhuangye/20070731/142625631.html 1.Log4j是什么? Log4j可以帮助调试(有时候debug是发挥不了作 ...

  4. JAVA中线程同步的方法(7种)汇总

    同步的方法: 一.同步方法 即有synchronized关键字修饰的方法. 由于java的每个对象都有一个内置锁,当用此关键字修饰方法时, 内置锁会保护整个方法.在调用该方法前,需要获得内置锁,否则就 ...

  5. 第九节:详细讲解Java中的泛型,多线程,网络编程

    前言 大家好,给大家带来详细讲解Java中的泛型,多线程,网络编程的概述,希望你们喜欢 泛型 泛型格式:ArrayList list= new ArrayList(); ArrayList list= ...

  6. 【转载】Java中的多线程超详细的总结

    引 如果对什么是线程.什么是进程仍存有疑惑,请先Google之,因为这两个概念不在本文的范围之内. 用多线程只有一个目的,那就是更好的利用cpu的资源,因为所有的多线程代码都可以用单线程来实现.说这个 ...

  7. 分析java中clone()方法 (转载+修改)

    Java中的clone() 方法 java所有的类都是从java.lang.Object类继承而来的,而Object类提供下面的方法对对象进行复制. protected native Object c ...

  8. Java中的多线程技术全面详解

    本文主要从整体上介绍Java中的多线程技术,对于一些重要的基础概念会进行相对详细的介绍,若有叙述不清晰或是不正确的地方,希望大家指出,谢谢大家:) 为什么使用多线程 并发与并行 我们知道,在单核机器上 ...

  9. 转:二十一、详细解析Java中抽象类和接口的区别

    转:二十一.详细解析Java中抽象类和接口的区别 http://blog.csdn.net/liujun13579/article/details/7737670 在Java语言中, abstract ...

随机推荐

  1. Shell编程—正则表达式

    1什么是正则表达式 1.1定义 正则表达式是你所定义的模式模板,Linux工具可以用它来过滤文本.Linux 工具(比如sed编辑器或gawk程序)能够在处理数据时使用正则表达式对数据进行模式匹配. ...

  2. java23种设计模式——八、组合模式

    目录 java23种设计模式-- 一.设计模式介绍 java23种设计模式-- 二.单例模式 java23种设计模式--三.工厂模式 java23种设计模式--四.原型模式 java23种设计模式-- ...

  3. 2020,最新APP重构:网络请求框架

    在现在的app,网络请求是一个很重要的部分,app中很多部分都有或多或少的网络请求,所以在一个项目重构时,我会选择网络请求框架作为我重构的起点.在这篇文章中我所提出的架构,并不是所谓的 最好 的网络请 ...

  4. 单元测试利器Mockito框架

    什么是Mock Mock 的中文译为仿制的,模拟的,虚假的.对于测试框架来说,即构造出一个模拟/虚假的对象,使我们的测试能顺利进行下去. Mock 测试就是在测试过程中,对于某些 不容易构造(如 Ht ...

  5. 利用 Python 写个七夕表白神器

    今天是七夕节,相比于现代人自创的 502,不对是 520,七夕才是中国传统意义上的情人节,本文分享几个 Python 表白程序,情侣可以现学现用,单身的话也可以先收藏一下,说不定下次就用上了. 很多人 ...

  6. 基于python tkinter的课堂点名小程序

    import datetime import json import os import random import tkinter as tk import openpyxl # 花名册文件名很多人 ...

  7. pymssql 介绍

    pymssql包是Python语言用于连接SQL Server数据库的驱动程序(或者称作DB API),它是最终和数据库进行交互的工具.SQLAlchemy包就是利用pymssql包实现和SQL Se ...

  8. markdown 语法总结(一)

    1.标题 代码 注:# 后面保持空格 # h1 ## h2 ### h3 #### h4 ##### h5 ###### h6 ####### h7 // 错误代码 ######## h8 // 错误 ...

  9. 初学WebGL引擎-BabylonJS:第3篇-方向纹理与相机

    [playground]-rotatuib abd scaling(方向) 源码 var createScene = function () { var scene = new BABYLON.Sce ...

  10. Hihocoder 小Hi小Ho扫雷作死一二三

    这里贴下不用枚举方格是否为雷的方法 a表示输入标号,初始值为-1代表未探知 b表示当前格子是否有雷,初始化为0,0表示未探知,1表示探知肯定有雷,2表示探知肯定无雷(我也不知道为什么不初始化为-1,作 ...