Java—线程,多线程,线程池研究之旅 three【奇葩之旅,come】(附源码解析)

一. 前言

two和threed是连着发的,所以时间较长,这篇文章包含源码解析,所以比较长,又不懂得可以直接留言,有错误地方也多多提出。
文章共计8966个字,有错误地方望大家海涵。
第一篇连接Java—线程,多线程,线程池研究之旅 one【奇葩之旅,come】
第二篇链接Java—线程,多线程,线程池研究之旅 two【奇葩之旅,come】

二. 多线程概念

1. 头号小弟干了什么

当大哥的头号小弟干活太慢,什么事都需要他一个人做,那么就会特别慢,同时效率很低,这时候可以多叫几个小弟来干活,那么如何叫小弟干活呢,当然是"打电话"!!!

专业点就是:让主线程运行之后,创建多个线程帮自己干活

2. 线程的实现方法(有三种类型的小弟)

实现线程的方式,常用的就三种,一类二接(一个类,俩个接口)

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

三. 多线程源码(直接上干货)

不废话直接扒光小弟让我们参观一下:

以下步骤一,步骤二…是代码的书写顺序,而每个步骤后面可能会跟一个或者多个代码片段,是此步骤代码所执行的部分源码片段,所以正确的阅读方式是:先看步骤一,然后看步骤一后面的1个或者多个代码片段,然后看步骤二,再看步骤二后面1个或者多个代码片段,以此类推!!!考点是对图片的理解,也是对代码的理解,需要重点看,当然你有更好的理解就按照你自己的理解去想就可以,考点也只是一个参考

1. Thread

详细流程

Thread这类型的小弟,东西还挺多,得慢慢研究~~~~~
一部分代码图片对应下面一段话,切勿看错,上干货,一定要注意观看顺序!!!

1) 步骤一

步骤一:

  • 第一种方式:
    Thread thread = new Thread();
  • 第二种方式:
    Thread thread = new Thread(“jjy”);

其他参数自己研究,上述是我比较常用的。

  1. 代码片段一

图1:
请添加图片描述

考点:

  1. 可以看到Thread类是实现Runnable接口的
  2. 当编译运行,类被加载时会优先执行static静态代码块里的内容,它只会被执行一次,可以看到最优先执行的是一个native的registerNatives方法【registerNatives 是一个 native 方法,用于注册本地方法(native methods)的映射。在 Java 中,native 方法是使用本地代码(通常是 C 或 C++ 编写)实现的方法,它们在 Java 中被声明为 native,并且在 Java 虚拟机之外执行。】
  1. 代码片段二

图2:我粘出来的,所以有错误提示,不用管
请添加图片描述

考点:

  1. 当你使用Thread时,根据不同的参数执行有参或者无参构造方法,执行init初始化方法
  2. 如果是继承Thread类,那么此处使用的是public Thread(){} ,如果给定线程名字,需要使用setName的方法。因为此处调用的是无参构造,如果你想给定默认名字,需要重写有参构造方法public TestThread(String name),一会看例子就明白了。
    4.public Thread(Runnable target) 这个也常用,图片里没有截取到,抱歉抱歉
  1. 代码片段三
    图3:
    请添加图片描述
    图4:
    请添加图片描述
    图5:
    请添加图片描述

考点:

  1. init方法,我们就不细看了,只需要关注一点,注意第二张图里代码:this.target = target; 如果使用实现Runable的方法,可以看到将传进来的Runable对象,赋值到类变量里面,第三张图片就是类私有变量,类型是Runable。
  2. 当然如果你使用的不是实现RUnable接口类这种方式,那你传进来的参数就是null值,这一步也就不用管了
2) 步骤二

步骤二:

  • 继承Thread类。重写run方法
  1. 代码片段四
    图6:
    在这里插入图片描述

考点:

  1. 如果使用继承Thread方法,那么target为null,你在你自己的类里会重写run方法
  2. 如果是实现Runable接口,那么target不为null,则此方法会调用Runable对象里的run()方法,实际上,也是你重写过后的run()方法

步骤三:

  • thread.start()
  1. 代码片段五
    图7:
    在这里插入图片描述

考点:

  1. 如果上面操作都做完了,那么会调用start()方法
  2. 可以看到此方法是上锁 synchronized 的
  3. 当你调用start()方法时,会调用一个start0()的方法,可以看到时native修饰的,是一个C写的代码,剩下的就会交由C来实现。

例子

正常用法:
在这里插入图片描述

重写用法:
在这里插入图片描述

1. Runnable

详细流程

看这一部分需要结合上面的Thread来看哦!

步骤一:

  • 实现Runable接口,重写run()方法
  1. 代码片段一
    这部分主要先讲解一下Runable接口
    图1:
    请添加图片s述

你没看错,小弟就长这样,但是你要问那到底是怎么实现的的呀,实现类是怎么运行的呀,不好意思去问神仙吧,至少我不知道!!!
有且只有一个抽象的run方法,无它。使用该接口必须实现run方法。

图片中的三段话我已经用“有道词典”帮各位翻译了,这三段话分别讲了:阐述这个类、作用、和使用方式,请欣赏:

第一段:Runnable接口应该由任何类实现,这些类的实例打算由线程执行。类必须定义一个名为run的无参数方法。
第二段:该接口旨在为希望在活动状态下执行代码的对象提供公共协议。例如,Runnable是由Thread类实现的。处于活动状态仅仅意味着线程已经启动并且尚未停止。
第三段:此外,Runnable提供了在不子类化Thread的情况下使类处于活动状态的方法。实现Runnable的类可以通过实例化Thread实例并将其自身作为目标传入,而无需子类化Thread即可运行。在大多数情况下,如果您只是计划重写run()方法而不重写其他Thread方法,则应该使用Runnable接口。这一点很重要,因为除非程序员打算修改或增强类的基本行为,否则类不应该被子类化。

仔细看其实里面的核心是 @FunctionalInterface 这个我也是查了好多资料,大概了解一下。如果不感兴趣的可以直接跳到下一块。

@FunctionalInterface 是 Java 中的一个注解,声明该接口类型是函数式接口,这意味着它只有一个抽象方法。函数式接口是 Java 函数式编程范式的一个关键概念,它们使得 lambda 表达式和方法引用的使用成为可能。
当一个接口标记了 @FunctionalInterface 注解时,Java 编译器会确保它只有一个抽象方法。如果接口有多个抽象方法,则编译器会报错。虽然这个注解是可选的,但是使用它来明确声明一个接口为函数式接口是一种良好的实践
请添加图片描述
这段代码演示了如何使用 lambda 表达式来定义 jjy() 方法的行为,同时使用了函数式接口

步骤2:

  • 第一种方式:
    Thread thread = new Thread(new Runnable() {@Override public void run() {}})
  • 第二种方式:
    TestRunable testRunable = new TestRunable (); 先获取自己写的类对象,这个类实现了Runable接口
    Thread thread = new Thread(testRunable); testRunable是实现Runable接口的对象
  1. 代码前段二
    由于用的是Thread类的方法,此处不再附上图片,对应Thread类中:代码前段一的图片

考点:

  1. 由于new了对象,会执行静态代码块的内容。
  1. 代码前段三
    由于用的是Thread类的方法,此处不再附上图片,对应Thread类中:代码前段二的图片

考点:

  1. 根据参数的不同,执行不同的有参构造方法
  2. 主要用public Thread(Runnable target) 和Thread(Runnable target, AccessControlContext acc)俩个方法,将实现RUnable接口的类对象放入其中即可
  1. 代码前段四
    由于用的是Thread类的方法,此处不再附上图片,对应Thread类中:代码前段三的图片

考点:
1.执行init()的方法,将实现Runable接口的类对象赋值给类私有变量。

  1. 代码前段五
    由于用的是Thread类的方法,此处不再附上图片,对应Thread类中:代码前段四的图片

考点:

  1. 当执行run()方法时,发现target变量并不为null,则调用此对象里的run()方法,也就是我们重写后的方法。
    这也是Thread和Runable的不同点,在调用run()方法是,调用的对象不同。!!!!(重点)

步骤三:

  • thread.start()
  1. 代码前段六
    由于用的是Thread类的方法,此处不再附上图片,对应Thread类中:代码片段五的图片

考点:

  1. 调用start()方法

例子

在这里插入图片描述

3. Callable

详细流程

先说明Callable小弟和Runable小弟的不同点:

  • Callable 接口 call 方法有返回值
  • Callable 接口 call 方 法允许抛出异常,可以获取异常信息
  • 其他无异,接下来我们按照顺序分析一下

步骤一:

  • 实现Runable接口,重写call()方法
  1. 代码前段一
    图一:
    在这里插入图片描述

考点:

  1. 通过图片可以看到Callable和Runable一样,都是接口形式,同时Runable里的run方法在这边改成了call方法,仔细看俩种方法会发现,Runable里的run()方法是抽象方法,无返回值,而Callable里的call()方法是普通方法,返回值是V泛型(可以在实现接口时指定),那为啥call方法可以有返回值,我们在下一部分细细分解研究。
  2. 同样使用了@FunctionalInterface 这一快去上面的Runable第一部分看就可以。

步骤二:

  • TestCallable testCallable = new TestCallable();
  • FutureTask futureTask = new FutureTask(testCallable);
  1. 代码前段二(重点!!!)

图二:
在这里插入图片描述

图三:
在这里插入图片描述

图四:
在这里插入图片描述

图五:
在这里插入图片描述

重点:

  • 这边是Callable的重点,我们先说为什么没有直接将testCallable对象直接放到Thread中,像new Thread(testCallable)这样。因为Thread()里面只能放Runable类的对象,而callable是不可以的,具体可以看Thread第二部分的图片。
  • 那么我们就需要转换一下,所以有了FutureTask,而FutureTask又是实现RunnableFuture接口,而RunnableFuture接口继承Runnable和Future的接口(注意只有接口之间才可以继承,类只能单继承,多实现)。通过图二、三、四、五可以看到几个类和接口相对应的关系
  • 所以FutureTask最终实现的还是Runable里面的Run方法,详细的下一部分讲。而FutureTask其他方法都是Future提供的。

这边我们还得研究一下FutureTask里面做了什么,这也是为什么Callable为什么有返回值:

图六:
在这里插入图片描述
图七:
在这里插入图片描述
图八:Executors类的方法,有很多其他参数方法,这边就不展示了,大家自己看
在这里插入图片描述
图九:Executors类的方法
在这里插入图片描述

考点:

  • 通过图六和图七,可以发现,当 new FutureTask(testCallable); 时,如果传入的是Callable接口类对象,初始化FutureTask类的私有变量Callable< V >和初始化状态,也就是图七。如果传入的是Runable接口类对象,那么就会调用Executors类里的callable方法,详细我们看图八,此时会创建一个RunableAdapter类对象,根据图十我们也知道这是一个静态内部类,这个类实现Callable接口,重写了call()方法。
  • 这个RunableAdapter内部类做了什么呢?答案就是,他将Runable里的run()方法执行了,并且将result结果进行返回,其实就是一个转换器,负责将实现Runable接口的类也同时返回一个结果,也算是弥补Runable没有返回结果的一个补救措施吧。

图十:
在这里插入图片描述

考点:

  • 我们回到正题,现在传入实现Callable接口的对象,然后run()方法里干了什么?
  • 可以看到,当c不是null时,且状态为NEW时,可以调用call()方法,也就是用户写的逻辑代码,然后将结果传入set()方法中
  • 这个set方法你们就自己看吧,一些结果逻辑判断然后赋值到类变量上,给后面使用,就不过多叙述了。

步骤三:

  • Thread thread = new Thread(futureTask);

根据上一步我们将FutureTask对象放到Thread中,因为FutureTask还是实现Runable接口的,是它的实现类。

  1. 代码前段三
    由于用的是Thread类的方法,此处不再附上图片,对应Thread类中:代码前段一的图片

考点:

  1. 由于new了对象,会执行静态代码块的内容。
  1. 代码前段四
    由于用的是Thread类的方法,此处不再附上图片,对应Thread类中:代码前段二的图片

考点:

  1. 根据参数的不同,执行不同的有参构造方法
  2. 主要用public Thread(Runnable target) 和Thread(Runnable target, AccessControlContext acc)俩个方法,将实现Runable接口的类对象放入其中即可
  1. 代码前段五
    由于用的是Thread类的方法,此处不再附上图片,对应Thread类中:代码片段三的图片

考点:
1.执行init()的方法,将实现Runable接口的类对象赋值给类私有变量。

  1. 代码片段六
    由于用的是Thread类的方法,此处不再附上图片,对应Thread类中:代码片段四的图片

考点:

  1. 当执行run()方法时,发现target变量并不为null,则调用此对象里的run()方法,也就是我们重写后的方法。
    这也是Thread和Runable的不同点,在调用run()方法是,调用的对象不同。!!!!(重点)

步骤三:

  • thread.start()
  1. 代码前段七
    由于用的是Thread类的方法,此处不再附上图片,对应Thread类中:代码前段五的图片

考点:

  1. 调用start()方法

重点:

  • 其实你会发现Callable这一整套流程前面和后面都是一样的,就是在FutureTask这不一样,这也是为什么Callable可以返回结果值和获取异常的原因,当然更深层次的还是需要你们自己挖掘,但是入门这些也就够了。

例子

实现Callable接口
在这里插入图片描述
实现Runable接口,同时也可以获取结果值。
在这里插入图片描述

四. 总结

先说这篇文章的总结,如果你全部看完,你就会发现,原来多线程三种实现方式如此简单,相互之间没有太大区别,其实这篇文章只是其中一小点,主要是为了梳理整个流程,也是为了让大家更好的理解这些小弟(线程创建方式)到底是怎么来的,每个人又有什么区别。

至于三种区别或者共同点,自己总结,或者自己查。
我个人认为,实现Runable接口的方式更常用一些,毕竟是接口形式,java里单继承限制太多了,所以Runable显得更容易扩展,其次Callable接口这种方式也是比较常用的,这就看你对结果和异常有没有需求了,不同的需求用不同的实现方式即可,但是一定要知道,三种方式都是通过Thread类来调用,至于执行start()方法那必然是底层的C或者C++实现。

这篇文章将会持续更新!!!

知识并非一日之寒,而需日积月累,所以学习或者了解到此就可。我们由浅入深,下一篇开始研究“线程池”,包括概念,如何调用(研究研究大哥,看看他们是如何变通的,嘿嘿)!!!

如果写的有误或者理解得不好,请一定告知,我会改正~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值