多线程系列(一) -线程技术入门知识讲解

这篇具有很好参考价值的文章主要介绍了多线程系列(一) -线程技术入门知识讲解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、简介

在很多场景下,我们经常听到采用多线程编程,能显著的提升程序的执行效率。例如执行大批量数据的插入操作,采用单线程编程进行插入可能需要 30 分钟,采用多线程编程进行插入可能只需要 5 分钟就够了。

既然多线程编程技术如此厉害,那什么是多线程呢?

在介绍多线程之前,我们还得先讲讲进程和线程的概念。

二、进程和线程

2.1、什么是进程?

从计算机角度来讲,进程是操作系统中的基本执行单元,也是操作系统进行资源分配和调度的基本单位,并且进程之间相互独立,互不干扰

例如,我们windows电脑中的 Chrome 浏览器是一个进程、WeChat 也是一个进程,正在操作系统中运行的.exe都可以理解为一个进程。

多线程系列(一) -线程技术入门知识讲解

2.2、什么是线程?

关于线程,比较官方的定义是,线程是进程中的⼀个执⾏单元,也是操作系统能够进行运算调度的最小单位,负责当前进程中程序的执⾏。同时⼀个进程中⾄少有⼀个线程,⼀个进程中也可以有多个线程,它们共享这个进程的资源,拥有多个线程的程序,我们也称为多线程编程。

举个例子,Chrome 浏览器和 WeChat 是两个进程,Chrome 浏览器进程里面有很多线程,例如 HTTP 请求线程、事件响应线程、渲染线程等等,线程的并发执行使得在浏览器中点击一个新链接从而发起 HTTP 请求时,浏览器还可以响应用户的其它事件。

2.3、进程和线程的关系

关于进程和线程,可能上面的解释过于抽象,还是很难理解,下面是一段出自阮一峰老师博客文章的介绍,可能描述不是非常严谨,但是足够形象,有助于我们对它们关系的理解。

  • 1.我们都知道,计算机的核心是 CPU,它承担了所有的计算任务。它就像一座工厂,时刻在运行;(CPU 类似于工厂
  • 2.假定工厂的电力有限,一次只能供给一个车间使用。也就是说,一个车间开工的时候,其他车间都必须停工。背后的含义就是,单个 CPU 一次只能运行一个任务;
  • 3.进程就好比工厂的车间,它代表 CPU 所能处理的单个任务。任一时刻,CPU 总是运行一个进程,其他进程处于非运行状态;(进程类似于车间
  • 4.一个车间里,可以有很多工人。他们协同完成一个任务;
  • 5.线程就好比车间里的工人。一个进程可以包括多个线程;(线程类似于工人
  • 6.车间的空间是工人们共享的,比如许多房间是每个工人都可以进出的。这象征一个进程的内存空间是共享的,每个线程都可以使用这些共享内存;(每个线程共享进程下的内存资源
  • 7.一个防止他人进入的简单方法,就是门口加一把锁。先到的人锁上门,后到的人看到上锁,就在门口排队,等锁打开再进去。这就叫"互斥锁"(Mutual exclusion,缩写 Mutex),防止多个线程同时读写某一块内存区域;(多个线程下可以通过互斥锁,实现资源独占
  • 8.还有些房间,可以同时容纳 n 个人,比如厨房。也就是说,如果人数大于 n,多出来的人只能在外面等着。这好比某些内存区域,只能供给固定数目的线程使用;
  • 9.这时的解决方法,就是在门口挂 n 把钥匙。进去的人就取一把钥匙,出来时再把钥匙挂回原处。后到的人发现钥匙架空了,就知道必须在门口排队等着了。这种做法叫做 "信号量"(Semaphore),用来保证多个线程不会互相冲突。(多个线程下可以通过信号量,实现互不冲突

不难看出,互斥锁 Mutex 是信号量 semaphore 的一种特殊情况(n = 1时)。也就是说,完全可以用后者替代前者。但是,因为 Mutex 较为简单,且效率高,所以在必须保证资源独占的情况下,还是采用这种方式。

2.4、为什么要引入线程?

早期的操作系统都是以进程作为独立运行的基本单位的,直到后期计算机科学家们又提出了更小的能独立运行的基本单位,也就是线程。

那为什么要引入线程呢?我们只需要记住这句话:线程又称为迷你进程,但是它比进程更容易创建,也更容易撤销

引入线程之后,可以将复杂的操作进一步分解,让程序的执行效率进一步提升

举个例子,进程就如同一个随时背着粮草和机枪的士兵,这样肯定会造成士兵的执行战斗的速度。因此,一个简单想法就是:分配两个人来执行,一个士兵负责随时背着粮草,另一个士兵负责抗机枪战斗,这样执行战斗的速度会大幅提升。这些轻装上阵的士兵,可以理解为我们上文提到的线程!

从计算机角度来说,由于创建或撤销进程时,系统都要为之分配或回收资源,如内存空间、I/O 设备等,需要较大的时间和空间开销。

为了减少进程切换的开销,把进程作为资源分配单位和调度单位这两个属性分开处理,即进程还是作为资源分配的基本单位,但是把调度执行与切换的责任交给线程,即线程成为独立调度的基本单位,它比进程更容易(更快)创建,也更容易撤销。

一句话总结就是:引入线程前,进程是资源分配和独立调度的基本单位。引入线程后,进程是资源分配的基本单位,线程是独立调度的基本单位,线程也是进程中的⼀个执⾏单元。

三、创建线程的方式

在 Java 里面,创建线程有以下两种方式:

  • 继承java.lang.Thread类,重写run()方法
  • 实现java.lang.Runnable接口,然后通过一个java.lang.Thread类来启动

不管是哪种方式,所有的线程对象都必须是Thread类或其⼦类的实例,每个线程的作⽤是完成⼀定的任务,实际上就是执⾏⼀段程序流,即⼀段顺序执⾏的代码,任务执行完毕之后就结束了。

在 Java 中,通过Thread类来创建并启动线程的步骤如下:

  • 1.定义Thread类的⼦类,并重写该类的run()方法
  • 2.通过Thread子类,初始化线程对象
  • 3.通过线程对象,调用start()方法启动线程

下面我们具体来看看创建线程的代码实践。

3.1、继承 Thread 类,重写 run 方法介绍

/**
 * 创建一个 Thread 子类
 */
public class Thread0 extends Thread {

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date());
            System.out.println(time + " 当前线程:" + Thread.currentThread().getName() + ",正在运行");
        }
    }
}
/**
 * 创建一个测试类
 */
public class ThreadTest0 {

    public static void main(String[] args) {
        // 初始化一个线程对象,然后启动线程
        Thread0 thread0 = new Thread0();
        thread0.start();

        for (int i = 0; i < 5; i++) {
            String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date());
            System.out.println(time + " 当前线程:" + Thread.currentThread().getName() + ",正在运行");
        }
    }
}

输出结果:

2023-08-23 17:58:03:726 当前线程:Thread-0,正在运行
2023-08-23 17:58:03:727 当前线程:Thread-0,正在运行
2023-08-23 17:58:03:726 当前线程:main,正在运行
2023-08-23 17:58:03:727 当前线程:Thread-0,正在运行
2023-08-23 17:58:03:727 当前线程:main,正在运行
2023-08-23 17:58:03:728 当前线程:Thread-0,正在运行
2023-08-23 17:58:03:728 当前线程:main,正在运行
2023-08-23 17:58:03:728 当前线程:Thread-0,正在运行
2023-08-23 17:58:03:728 当前线程:main,正在运行
2023-08-23 17:58:03:728 当前线程:main,正在运行

从执行时间上可以看到,main线程和Thread-0线程交替运行,效果十分明显!

所谓的多线程,其实就是两个及以上线程的代码可以同时运行,而不必一个线程需要等待另一个线程内的代码执行完才可以运行。

对于单核 CPU 来说,是无法做到真正的多线程的;但是对于多核 CPU 来说,在一段时间内,可以执行多个任务的,由于 CPU 执行代码时间很快,所以两个线程的代码交替执行看起来像是同时执行的一样,具体执行某段代码多少时间,就和分时机制系统有关了。

分时机制系统,简单的说,就是将 CPU 时间划分为多个时间片,操作系统以时间片为单位来执行各个线程的代码,越好的 CPU 分出的时间片越小。

例如某个时段, CPU 将 1 秒划分成 50 个时间片,1 个时间片耗时 20 ms,每个时间片均进行线程切换,也就是说 1 秒可以执行 50 个任务,给人的感觉好像计算机能同时处理多件事情,其实是 CPU 执行任务速度太快给人产生的错觉感。

3.2、实现 Runnable 接口,然后通过 Thread 类来启动介绍

/**
 * 实现 Runnable 接口
 */
public class Thread2 implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date());
            System.out.println(time + " 当前线程:" + Thread.currentThread().getName() + ",正在运行");
        }
    }
}
/**
 * 创建一个测试类
 */
public class ThreadTest2 {

    public static void main(String[] args) {
        // 通过一个Thread来启动线程
        Thread thread2 = new Thread(new Thread2());
        thread2.start();

        for (int i = 0; i < 5; i++) {
            String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date());
            System.out.println(time + " 当前线程:" + Thread.currentThread().getName() + ",正在运行");
        }
    }
}

输出结果:

2023-08-23 18:30:28:664 当前线程:Thread-0,正在运行
2023-08-23 18:30:28:666 当前线程:Thread-0,正在运行
2023-08-23 18:30:28:666 当前线程:Thread-0,正在运行
2023-08-23 18:30:28:664 当前线程:main,正在运行
2023-08-23 18:30:28:666 当前线程:Thread-0,正在运行
2023-08-23 18:30:28:667 当前线程:Thread-0,正在运行
2023-08-23 18:30:28:668 当前线程:main,正在运行
2023-08-23 18:30:28:668 当前线程:main,正在运行
2023-08-23 18:30:28:668 当前线程:main,正在运行
2023-08-23 18:30:28:668 当前线程:main,正在运行

效果跟上面介绍的一样,如果循环的打印次数越多,效果越明显!

四、线程状态

下图是一张从操作系统角度划分的线程模型状态!

多线程系列(一) -线程技术入门知识讲解

线程被分为五种状态,各个状态说明如下:

  • 1.新建状态:表示创建了一个新的线程对象,例如Thread thread = new Thread()
  • 2.就绪状态:比如调用线程的start()方法,就会处于就绪状态,也被称为可执行状态,随时可能被 CPU 调度执行
  • 3.运行状态:获得了 CPU 时间片,执行程序代码。需要注意的是,线程只能从就绪状态进入到运行状态
  • 4.阻塞状态:因为某种原因出现了阻塞,线程放弃对 CPU 的使用权,停止执行,直到阻塞事件结束,重新进入就绪状态才有可能再次被 CPU 调度。
  • 5.结束状态:线程里面的方法正常执行结束或者因为某种异常退出了,则该线程结束生命周期

针对操作系统的线程模型,Java 进行部分封装和扩充,JVM 中的线程状态总共有六种,它们之间的关系,可以用如下图来表示:

多线程系列(一) -线程技术入门知识讲解

各个状态说明如下:

  • 1.新建状态(NEW):新创建了一个线程对象
  • 2.运行状态(RUNNABLE):Java 线程中将就绪状态和运行中两种状态,笼统的称为“运行”。线程对象创建后,调用了该对象的start()方法,该线程处于就绪状态,获得 CPU 时间片后变为运行中状态
  • 3.阻塞状态(BLOCKED):因为某种原因,线程放弃对 CPU 的使用权,停止执行,直到进入就绪状态才有可能再次被 CPU 调度。比如线程在获得synchronized同步锁失败后,会把线程放入锁池中,线程进入同步阻塞状态。
  • 4.等待状态(WAITING):处于这种状态的线程不会被分配 CPU 执行时间,它们要等待被显式地唤醒,否则会处于无限期等待的状态。比如运行状态的线程执行wait方法,会把线程放在等待队列中,直到被唤醒或者因异常自动退出
  • 5.超时等待状态(TIMED_WAITING):处于这种状态的线程不会被分配 CPU 执行时间,不过无须无限期等待被其他线程显式地唤醒,在到达一定时间后它们会自动唤醒。比如运行状态的线程执行Thread.sleep(1000)方法,当到达目标时间后,会自动唤醒或者因异常自动退出
  • 6.终止状态(TERMINATED):表示该线程已经执行完毕,处于终止状态的线程不具备继续运行的能力

五、小结

本文主要围绕进程和线程的一些基础知识,进行简单的入门知识总结。

线程的特征和进程差不多,进程有的它基本都有。

相对于进程而言,线程更加的轻量化,主要承担任务的执行工作,优点如下:

  • 一个进程中可以同时拥有多个线程,这些线程共享该进程的资源。我们知道进程间的通信必须请求操作系统服务(因为 CPU 要切换到内核态),开销很大。而同进程下的线程间通信,无需操作系统干预,开销更小
  • 线程间可以并发执行任务,线程间的并发比进程的开销更小,系统并发性更好
  • 在多 CPU 环境下,各个线程也可以分派到不同的 CPU 上并行执行
  • 通过多线程编程,可以显著的提升程序任务的执行效率

不过线程也有缺点:

  • 当程序编程不合理,多个线程发生较长时间的等待或资源竞争时,可能会出现死锁
  • 等候使用共享资源时可能会造成程序的运行速度变慢。这些共享资源主要是独占性的资源,如打印机、IO 设备等

总的来说,进程和线程各有各优势,站在操作系统的设计角度而言,可以归结为以下几点:

  • 采用多进程方式,可以保证多个任务同时运行;
  • 采用多线程方式,可以将单个任务分成不同的部分进行执行;
  • 提供协调机制,防止进程之间和线程之间产生冲突,同时允许进程之间和线程之间共享资源,以充分的利用系统资源

整篇内容难免有描述不对的地方,欢迎网友留言指出!

六、参考

1、飞天小牛肉 - 五分钟扫盲:进程与线程基础必知

2、潘建南 - Java线程的6种状态及切换文章来源地址https://www.toymoban.com/news/detail-825460.html

到了这里,关于多线程系列(一) -线程技术入门知识讲解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • MyBatis基础知识和快速入门、MyBatis核心配置文件讲解

    什么是Mybatis MyBatis 是一个优秀的基于java的 持久层框架 ,它内部封装了jdbc,使开发者只需要关注sql语句本身,而不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。 mybatis通过xml或注解的方式将要执行的各种 statement配置起来,并通过java对象和statement中

    2024年02月04日
    浏览(106)
  • adb shell模拟发送安卓广播的入门知识和100个实例讲解

    adb shell模拟发送安卓广播的入门知识和实例讲解: 广播是一种Android系统提供的一种机制,用于在系统中传递事件或消息。广播可以是系统级别的,也可以是应用级别的。系统级别的广播可以被所有应用接收,应用级别的广播只能被同一应用中的组件接收。 广播分为两种类型

    2024年02月09日
    浏览(52)
  • Dubbo快速入门 —— 基于SpringBoot Starter 开发微服务应用案例 + 知识讲解

                                      💧 D u b b o 快 速 入 门 — — 基 于 S p r i n g B o o t S t a r t e r 开 发 微 服 务 应 用 案 例 + 知 识 讲 解 color{#FF1493}{Dubbo快速入门 —— 基于SpringBoot Starter 开发微服务应用案例 + 知识讲解} D u b b o 快 速 入 门 — — 基 于 S p r

    2024年02月08日
    浏览(52)
  • Spring入门系列:浅析知识点

    讲解Spring之前,我们首先梳理下Spring有哪些知识点可以进行入手源码分析,比如: Spring IOC依赖注入 Spring AOP切面编程 Spring Bean的声明周期底层原理 Spring 初始化底层原理 Spring Transaction事务底层原理 通过这些知识点,后续我们慢慢在深入Spring的使用及原理剖析,为了更好地理

    2023年04月10日
    浏览(40)
  • QT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识)

    目录 一、Qt概述 1.1 什么是Qt 1.2 Qt的发展史 1.3 Qt的优势 1.4 Qt版本 1.5 成功案例 二、创建Qt项目 2.1 使用向导创建 2.2 一个最简单的Qt应用程序 2.2.1 main函数中 2.2.2 类头文件 2.3 .pro文件 2.4 命名规范  2.5 QtCreator常用快捷键 三、Qt按钮小程序 3.1按钮的创建和父子关系 3.2 Qt窗口坐标

    2024年02月09日
    浏览(50)
  • uniapp快速入门系列(1)- 概述与基础知识

    1.1.1 什么是uniapp? uniapp是一款基于Vue.js框架的跨平台应用开发框架,它可以让开发者使用一套代码,同时构建多个平台(包括但不限于微信小程序、支付宝小程序、抖音小程序等)的应用程序。 在过去,我们可能需要分别使用不同的技术和工具来开发不同平台的应用,但是

    2024年02月07日
    浏览(53)
  • 【C++初阶】一、入门知识讲解(C++关键字、命名空间、C++输入&输出、缺省参数、函数重载)

    ========================================================================= 相关代码gitee自取 : C语言学习日记: 加油努力 (gitee.com)  ========================================================================= 接上期 : 【数据结构初阶】十一、归并排序(比较排序)的讲解和实现 (递归版本 + 非递归版本 -- C语言实

    2024年02月05日
    浏览(52)
  • 详解爬虫基本知识及入门案列(爬取豆瓣电影《热辣滚烫》的短评 详细讲解代码实现)

    目录 前言什么是爬虫? 爬虫与反爬虫基础知识 一、网页基础知识  二、网络传输协议 HTTP(HyperText Transfer Protocol)和HTTPS(HTTP Secure)请求过程的原理? 三、Session和Cookies Session Cookies Session与Cookies的区别与联系  四、Web服务器Nginx 五、代理IP 1、代理IP的原理 2. 分类 3. 获取途

    2024年04月29日
    浏览(65)
  • 【C++初阶】二、入门知识讲解(引用、内联函数、auto关键字、基于范围的for循环、指针空值nullptr)

    ========================================================================= 相关代码gitee自取 : C语言学习日记: 加油努力 (gitee.com)  ========================================================================= 接上期 : 【C++初阶】一、入门知识讲解 (C++、命名空间、C++输入输出、缺省参数、函数重载)-

    2024年02月04日
    浏览(68)
  • STC8H系列单片机入门教程之ADC基础知识(四)

    目录 一、A/D转换过程 二、ADC转换流程图 三、采样定理 四、ADC基本参数 4.1、分辨率 4.2、采样速率 4.3、转换时间 4.4、量程  4.5、最低有效位 五、静态参数 5.1、微分非线性 5.2、积分非线性 六、逐次逼近型模数转换器 七、ADC常用分压电路 八、示例代码 ADC即模数转换器,用来

    2024年04月11日
    浏览(59)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包