Start 方法源码深究——模板方法设计模式

news2024/11/26 0:25:29

目录

  • 一. 🦁 前言
    • 1.1 New状态
    • 1.2 Runnable
    • 1.3 Runing
    • 1.4 Block状态
    • 1.5 Terminated状态
  • 二. 🦁 线程 start 方法源码剖析
    • 2.1 虚拟机调用run方法执行线程
    • 2.2 最少有两个线程在执行
    • 2. 3 不可以重复执行
    • 2.4 start方法体
  • 三. 🦁 模板方法设计模式
    • 3.1 基本概念
    • 3.2 自定义一个场景实现模板方法模式
    • 3.3 钩子函数

权限管理

一. 🦁 前言

先来了解一下线程启动到生命结束的大致过程:
在这里插入图片描述

1.1 New状态

当我们用关键字 new 创建一个 Thread 对象时 ,此时它并不处于执行状态 ,因为没有调用 start 方法启动该线程 ,此时线程的状态为 new 状态 ,它只是 Thread 对象的状态(相当于new 了一个Thread对象),因为在没有 start() 之前 ,该线程根本不存在 ,new 状态时通过 start 方法进入 Runnable 状态。

1.2 Runnable

线程对象进入 Runnable 状态必须调用 start 方法 ,此时才真正地在 JVM 进程中创建 了一个线程。
线程一经启动就会立即得到执行吗?
答案是否定的 ,线程的运行与否和进程一 样都要听令于 CPU 的调度 ,所以我们把这个中间状态称为可执行状态(Runnable) ,也就是 说它具备执行的资格 ,但是并没有真正的执行起来而是在等待 CPU 的调度
由于存在 Running 状态 ,所以不会直接进入 Blocked 状态和 Terminated 状态 ,即使是在线程的执行逻辑中调用 wait、 sleep 或者其他block 的 IO 操作等 ,也必须先获得 CPU 的调度执行权才可以 ,严格来讲 ,Runnable 的线程只能意外终止或者进入 Running 状态。

1.3 Runing

一旦 CPU 通过轮询或其他方式从任务可执行队列中选中了线程 ,那么此时它才能真正 地执行 run 方法里的逻辑代码。在该状态中 ,线程的状态可以发生如下的状态转换。

  • 直接进入 Terminated 状态 ,比如调用 JDK 已经不推荐使用的 stop 方法或者意外死亡;
  • 进入 Blocked 状态 ,比如调用 sleep 或者 wait 方法而加入了 waitSet 中;
  • 进行某个阻塞的 IO 操作 ,比如因网络数据的读写而进入了 Blocked ;
  • 获取某个锁资源 ,从而加入到该锁的阻塞队列中而进入了 Blocked ;
  • 由于 CPU 的调度器轮询使该线程放弃执行 ,进入 Runnable 状态;
  • 线程主动调用 yield 方法 ,放弃 CPU 执行权 ,进入 Runnable;

1.4 Block状态

上面已经介绍了线程进入 Blocked 状态的原因 ,这里不在赘述。线程在 Blocked 状态 中可以切换至如下几个状态。

  • 直接进入 Terminated 状态 ,比如调用 JDK 已经不推荐使用的 stop 方法或者意外死 亡;
  • 线程阻塞的操作结束 ,比如读取了想要的数据字节进入到 Runnable;
  • 线程完成了指定时间的休眠 ,进入到了 Runnable ;
  • wait 中的线程被其他线程 notify/notifyall 唤醒 ,进入 Runnable ;
  • 线程获取到了某个锁资源 ,进入 Runnable;
  • 线程在阻塞过程中被打断 ,比如其他线程调用了 interrupt 方法 ,进入 Runnable。

1.5 Terminated状态

Terminated 是一个线程的最终状态 ,在该状态中线程将不会切换到其他任何状态 ,线程进入 Terminated ,意味着该线程的生命周期都结束了 ,下面这些情况会使线程进入 Terminated 状态。

  • 线程运行正常结束 ,结束生命周期;
  • 线程运行出错 ,意外结束;
  • JVM Crash,导致所有的线程都结束。

二. 🦁 线程 start 方法源码剖析

    /**
     * 2.1 Causes this thread to begin execution; the Java Virtual Machine
     * calls the {@code run} method of this thread.
     * <p> 
     * 2.2 The result is that two threads are running concurrently: the
     * current thread (which returns from the call to the
     * {@code start} method) and the other thread (which executes its
     * {@code run} method).
     * <p>
     * 2.3 It is never legal to start a thread more than once.
     * In particular, a thread may not be restarted once it has completed
     * execution.
     *
     * @throws     IllegalThreadStateException  if the thread was already started.
     * @see        #run()
     * @see        #stop()
     */
    public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

    private native void start0();

所谓源码之下无秘密,源码的注释里面已经跟我们解释了start()方法的作用以及相关要点,我们根据上面标注的序号,一个一个来看:

2.1 虚拟机调用run方法执行线程

当调用start()后,JVM虚拟机则会调用run()方法,线程开始进入可执行状态。

2.2 最少有两个线程在执行

在main方法中,新起的线程其实都是主线程下开辟的子线程,子线程在执行时,主线程一定也会执行,他俩交替并行执行。则此时则一共有两条线程在执行(只有一条子线程的情况下)。

2. 3 不可以重复执行

子线程在调用过程中,不允许被重复执行两次,即不能重复调用两次start()。
即使子线程结束后,也是不能重新被调用的,否则会抛出线程不合法异常(IllegalThreadStateException)。

2.4 start方法体

我们现在来看一下start方法体里面的流程:

  • 状态检验
    这里会给我们来个状态检验,检验线程是否是初次执行,如果是初始执行的,则threadStatus = 0,表示NEW新建状态,否则,抛出IllegalThreadStateException();
 if (threadStatus != 0)
            throw new IllegalThreadStateException();

实践一下:启动一个start(),打断点调试:
在这里插入图片描述
在这里插入图片描述
如果再调用一次start()
在这里插入图片描述
结果如下:
在这里插入图片描述
此时threadStatus = 5,再执行则会抛出异常了。
在这里插入图片描述

  • 添加线程进线程组
group.add(this);
  • 调用c++的native方法,执行run()
 boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

这里调用c++的native方法,执行run(),如果启动失败,则从线程组中移除当前线程。

所以这里其实就是为了执行run(),但是我们前面写的时候是直接调用了start()方法,为什么会这样呢?

这里就涉及到一个叫 模板方法的 设计模式。

三. 🦁 模板方法设计模式

3.1 基本概念

定义了一个操作算法的框架,将一些步骤延迟到子类中执行使得子类可以不改变一
个算法的结构即可重定义该算法的某些步骤
还可以通过子类来决定父类算法中某个步骤是
否执行,实现子类对父类的反向控制

比如启动线程 start 的行为由 JVM 虚拟机来决定,但是该线程做什么事情就不由它来 决定了。对外提供了 run 方法,内部就是你自己线程的执行逻辑。

3.2 自定义一个场景实现模板方法模式

拿做西红柿炒蛋的例子来说明,同样的步骤其实不同的人做出来的饭是不一样的。就拿自己
和五星级大厨来比较吧。比如就做个西红柿鸡蛋,我们可以简单地定义一下步骤:

  • 第一步:放油
  • 第二步:放鸡蛋
  • 第三步:放西红柿

如果按照模板方法的思路去构建,我们需要剥离出两个角色:

  1. 模板方法:父类,定义一系列方法,提供骨架;
  2. 具体类:实现模板方法类提供的骨架。根据自己的个性化需求重写模板方法
/**
 * 模板方法类
 */
public abstract class Cook {
    abstract void oil();        //放油
    abstract void egg();        //打鸡蛋
    abstract void tomato();     //放西红柿
    
    // 封装具体的行为:做饭
    public final void cook(){
        oil();
        egg();
        tomato();
    }
}
  • 继承模板方法类
public class MyCooking extends Cook{
    @Override
    void oil() {
        System.out.println("我:"+"适量油!");
    }

    @Override
    void egg() {
        System.out.println("我:"+"搅拌适量鸡蛋!");
    }

    @Override
    void tomato() {
        System.out.println("我:"+"放西红柿!");
    }
}

public class Chef extends Cook{
    @Override
    void oil() {
        System.out.println("大厨:"+"适量油!");
    }

    @Override
    void egg() {
        System.out.println("大厨:"+"搅拌适量鸡蛋!");
    }

    @Override
    void tomato() {
        System.out.println("大厨:"+"放西红柿!");
    }
}
  • 实现:
public class Test {
    public static void main(String[] args) {
        new MyCooking().cook();
        new Chef().cook();
        System.out.println("我和大厨做得一样好吃!");
    }
}

在这里插入图片描述

3.3 钩子函数

这里就实现了模板设计模式,但是似乎还没做到通过子类来决定父类算法中某个步骤是 否执行,实现子类对父类的反向控制,这里我们需要通过钩子函数去实现:

  • 我们在模板方法里面添加一个钩子函数,让他决定炒菜过程中是否要放油:

/**
 * 模板方法类
 */
public abstract class Cook {    
    abstract void oil();        //放油
    abstract void egg();        //打鸡蛋
    abstract void tomato();     //放西红柿

    boolean isOil(){
        return true;
    }

    // 封装具体的行为:做饭
    public final void cook(){
        if (isOil())
            oil();
        egg();
        tomato();
    }
}

  • 在其中一个实现类中,重写isOil():
public class MyCooking extends Cook{

    private boolean isAddOilFlag = true;

    @Override
    boolean isOil() {
        return this.isAddOilFlag;
    }

    @Override
    void oil() {
        System.out.println("我:"+"适量油!");
    }

    @Override
    void egg() {
        System.out.println("我:"+"搅拌适量鸡蛋!");
    }

    @Override
    void tomato() {
        System.out.println("我:"+"放西红柿!");
        System.out.println("没放油,菜糊啦!!!");
    }
}
  • 测试结果:
    在这里插入图片描述

在这里插入图片描述

🦁 其它优质专栏推荐 🦁

🌟《Java核心系列(修炼内功,无上心法)》: 主要是JDK源码的核心讲解,几乎每篇文章都过万字,让你详细掌握每一个知识点!

🌟 《springBoot 源码剥析核心系列》:一些场景的Springboot源码剥析以及常用Springboot相关知识点解读

欢迎加入狮子的社区:『Lion-编程进阶之路』,日常收录优质好文

更多文章可持续关注上方🦁的博客,2023咱们顶峰相见!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1029344.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

李沐深度学习记录1:零碎知识记录、08线性回归

简要记录&#xff0c;以便查阅~ 一、零碎知识 x.numel()&#xff1a;看向量或矩阵里元素个数 A.sum()&#xff1a;向量或矩阵求和&#xff0c;axis参数可对某维度求和&#xff0c;keepdims参数设置是否保持维度不变 A.cumsum&#xff1a;axis参数设置沿某一维度计算矩阵累计和…

05_Bootstrap插件02

7 小标签 通过 .label 实现小标签&#xff0c;用于提示类。 <h1>h1标题 <span class"label label-default">标签</span></h1> <h2>h2标题<span class"label label-default">标签</span></h2> <h3&g…

精品Python思政素材数据库在线学习资源网

《[含文档PPT源码等]精品基于Python实现的思政素材数据库设计与实现》该项目含有源码、文档、PPT、配套开发软件、软件安装教程、项目发布教程等 软件开发环境及开发工具&#xff1a; 开发语言&#xff1a;python 使用框架&#xff1a;Django 前端技术&#xff1a;JavaScri…

Linux:GlusterFS 集群

GlusterFS介绍 1&#xff09;Glusterfs是一个开源的分布式文件系统,是Scale存储的核心,能够处理千数量级的客户端.在传统的解决 方案中Glusterfs能够灵活的结合物理的,虚拟的和云资源去体现高可用和企业级的性能存储. 2&#xff09;Glusterfs通过TCP/IP或InfiniBand RDMA网络链…

2023年9月21日

完善登录界面的注册登录功能 头文件1 #ifndef MAINWINDOW_H #define MAINWINDOW_H#include <QMainWindow> #include <QPushButton> #include <QLineEdit> #include <QLabel> #include <QMovie> #include <QDebug> #include <QMessage…

【计算机网络】深入理解TCP协议二(连接管理机制、WAIT_TIME、滑动窗口、流量控制、拥塞控制)

TCP协议 1.连接管理机制2.再谈WAIT_TIME状态2.1理解WAIT_TIME状态2.2解决TIME_WAIT状态引起的bind失败的方法2.3监听套接字listen第二个参数介绍 3.滑动窗口3.1介绍3.2丢包情况分析 4.流量控制5.拥塞控制5.1介绍5.2慢启动 6.捎带应答、延时应答 1.连接管理机制 正常情况下&…

记一次 .NET 某餐饮小程序 内存暴涨分析

一&#xff1a;背景 1. 讲故事 前些天有位朋友找到我&#xff0c;说他的程序内存异常高&#xff0c;用 vs诊断工具 加载时间又太久&#xff0c;让我帮忙看一下到底咋回事&#xff0c;截图如下&#xff1a; 确实&#xff0c;如果dump文件超过 10G 之后&#xff0c;市面上那些可…

ESP8266 WiFi物联网智能插座—项目简介

目录 1、项目背景 2、设备节点功能 3、上位机功能 物联网虽然能够使家居设备和系统实现自动化、智能化管理&#xff0c;但是依然需要依靠更为先进的终端插座作为根本保障&#xff0c;插座是所有家用电器需要使用的电源设备&#xff0c;插座的有序智能管理&#xff0c;对于实…

SpringMVC初级

文章目录 一、SpringMVC 概述二、springMVC步骤1、新建maven的web项目2、导入maven依赖3、创建controller4、创建spring-mvc.xml配置文件&#xff08;本质就是spring的配置件&#xff09;5、web.xml中配置前端控制器6、新建a.jsp文件7、配置tomcat8、启动测试 三、工作流程分析…

echart在折线显示横纵(横纵线沿着折线展示)

产品有个需求&#xff0c;需要在echart折线上展示横纵向坐标系&#xff0c;echart的axisPointer默认是展示在鼠标当前位置的&#xff0c;不符合需求&#xff0c;所以是使用markline实现的 在线例子和源码 先上效果图 实现思路 横纵线的x轴线是比较容易的&#xff0c;因为ech…

ONES 全球化启航,用软件服务全球企业

美西太平洋时间2023年9月6日至9月8日&#xff0c;SaaStr Annual 2023 大会在美国旧金山举办。作为全球最大规模、最具影响力的 SaaS 行业盛会&#xff0c;SaaStr 吸引了上万名来自世界各地的 SaaS 行业从业者&#xff0c;ONES 也作为展商之一参与其中。 在为期三天的大会期间&a…

实用的嵌入式编码技巧:第三部分

每个触发器都有两个我们在风险方面违反的关键规格。“建立时间”是时钟到来之前输入数据必须稳定的最小纳秒数。“保持时间”告诉我们在时钟转换后保持数据存在多长时间。 这些规格因逻辑设备而异。有些可能需要数十纳秒的设置和/或保持时间&#xff1b;其他人则需要少一个数量…

DC/DC开关电源学习笔记(十)Buck降压电路仿真及工程应用实例

(十)Buck降压电路仿真及工程应用实例 1. 仿真应用实例1.1 案例一1.2 案例二2. 工程应用实例2.1 数字DC/DC应用实例2.2 模拟DC/DC应用实例1. 仿真应用实例 1.1 案例一 仿真技术要求输入:输入电压30~90V,输出电压28V,输出电流最大10A,开关频率100KHz。我们按照参数极限工…

大语言模型之十一 Transformer后继者Retentive Networks (RetNet)

在《大语言模型之四-LlaMA-2从模型到应用》的LLama-2推理图中可以看到&#xff0c;在输入“你好&#xff01;”时&#xff0c;是串行进行的&#xff0c;即先输入“你”这个token&#xff0c;然后是“好”&#xff0c;再然后是“&#xff01;”token&#xff0c;前一个token需要…

【QT】day5

1.登录注册和数据库联动 三个头文件 #ifndef DEMO_H #define DEMO_H#include <QWidget> #include <QSqlDatabase> //数据库管理类 #include <QSqlQuery> //执行sql语句的类 #include <QSqlRecord> //数据库记录的类 #include <QMessageBox>…

传统的经典问题 Java 的 Interface 是干什么的

传统的经典问题 Java 的 Interface 是干什么 解答 上面的这个问题应该还是比较好回答的吧。 只要你做过 Java &#xff0c;通常 Interface 的问题多多少少会遇到&#xff0c;而且可能会遇到一大堆。 在JAVA编程语言中是一个抽象类型&#xff08;Abstract Type&#xff09;&…

SpringBoot之静态资源规则与定制化

文章目录 前言一、静态资源访问二、静态资源访问前缀三、webjar资源处理的默认规则 四、welcome与favicon功能1.欢迎页支持欢迎页处理规则 2.自定义Favicon 五、补充总结 前言 本文主要介绍关于SpringBoot中Web开发的简单功能。 一、静态资源访问 只要静态资源放在类路径下&am…

物联网安全优秀实践:2023年设备保护指南

物联网的发展可谓是革命性的&#xff0c;数十亿台设备实时互连、通信和共享数据。因此&#xff0c;考虑物联网安全的最佳实践至关重要。 物联网的重要性日益上升 在数字时代&#xff0c;物联网(IoT)已成为一股革命力量&#xff0c;重塑了企业运营和个人生活方式。从调节家庭温…

基于win32实现TB登陆滑动验证

这里写目录标题 滑动验证触发条件&#xff1a;失败条件&#xff1a;解决方法:清除cooKie 滑动验证方式一&#xff1a;win32 api获取窗口句柄&#xff0c;选择固定位置 成功率高方式二&#xff1a; 原自动化滑动&#xff0c;成功率中 案例 先谈理论&#xff0c;淘宝 taobao.com …

A Span-based Multi-Modal Attention Network for joint entity-relationextraction

原文链接&#xff1a; https://www.sciencedirect.com/science/article/pii/S0950705122013247?via%3Dihub Knowledge-Based Systems 2023 介绍 作者认为当前基于span的关系提取方法都太关注于span内部的语义&#xff0c;忽略了span与span之间以及span与其他模态之间&#xff…