我的编程空间,编程开发者的网络收藏夹
学习永远不晚

多线程的初识和创建

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

多线程的初识和创建

请添加图片描述

✨个人主页:bit me👇
✨当前专栏:Java EE初阶👇
✨每日一语:知不足而奋进,望远山而前行。

目 录

💤一. 认识线程(Thread)

🍎1. 线程的引入

  • 根据我们前面学的进程,为什么要有多个进程呢?为了并发编程,他的 CPU 单个核心已经发挥到极致了,想要提升算力,就得使用多个核心。
  • 引入并发编程,最大的目的就是为了能够充分的利用好 CPU 的多核资源。使用多进程这种编程模型,是完全可以做到。并发编程,并且也能够使 CPU 多核被充分利用,但是在有些情急下,会存在问题,例如:需要频繁的创建 / 销毁进程,这个时候就会比较低效!!!
  • 例如,你写了个服务器程序,服务器要同一时刻给很多客户提供服务的,就需要用到并发编程,典型的做法,就是每个客户端给他分配一个进程,提供一对一服务。

创建 / 销毁进程,本身就是一个比较低效的操作
1. 创建 PCB
2. 分配系统资源(尤其是内存资源)分配资源,就比较消耗时间了!(这个是在系统内核资源管理模块进行一系列操作的...)
3. 把 PCB 加入到内核的双向链表中

为了提高这个场景下的效率,就引入了 " 线程 ",线程其实也叫做 " 轻量级进程 "

🍏2. 线程是什么?

一个线程其实是包含在进程中的(一个进程里面可以有很多个线程),每个线程其实也有自己的 PCB (一个进程里面可能就对应多个 PCB),同一个进程里面的多个线程之间,共用一份系统资源(意味着,新创建的线程,不必重新给他分配系统资源,只需要复用之前的即可!)

因此,创建线程只需要
1. 创建 PCB
2. 把 PCB 加入到内核的链表中

这就是线程相对于进程做出的重大改进!!!也就是线程更轻量的原因!

  • 创建线程比创建进程更快,开销更小.
  • 销毁线程比销毁进程更快,开销更小.
  • 调度线程比调度进程更快.

线程,是包含在进程内部的 " 逻辑执行流 "(线程可以执行一段单独的代码,多个线程之间,是并发执行的)

操作系统进行调度的时候,其实也是以 " 线程为单位 " 来进行调度的(系统内核不认进程 / 线程,只认 PCB)

如果把进程比作工厂,线程就是工厂内部的流水线

🍋3. 进程和线程之间的区别

  • 进程是包含线程的,线程是在进程内部的
  • 每个进程有独立的虚拟地址空间(进程之间的资源是独立的,进程的独立性),也有自己独立的文件描述符表;同一个进程(不同进程里面的不同线程,则没有共享的资源)的多个线程之间,则共用这一份虚拟地址空间和文件描述符表(线程之间系统资源是共享的)
  • 进程是操作系统中资源分配的基本单位,线程是操作系统中,调度执行的基本单位
  • 多个进程同时执行的时候,如果一个进程挂了,一般不会影响到别的进程;同一个进程内的多个线程之间,如果一个线程挂了,很可能把整个进程带走,其他同进程里的线程也就没了

 

💨二. 第一个多线程程序

🏐1. Java中线程的认识和基本操作

在 Java 中即使是一个最简单的 “hello”,其实在运行的时候也涉及到线程了,一个进程里至少会有一个线程

public static void main(String[] args) {    System.out.println("hello");}

运行上面的程序,操作系统就会创建一个 Java 进程,在这个 Java 进程里就会有一个线程(主线程)调用 main 方法;虽然在上述代码中,我们并没有手动的创建其他线程,但是 Java 进程在运行的时候,内部也会创建多个线程。

注:

谈到多进程的时候,经常会谈到 “父进程” “子进程”。进程 A 里面创建了进程 B,A 是 B 的父进程,B 是 A 的子进程。但是在多线程里面,没有 “父线程” “子线程” 这种说法,线程之间的地位是对等的!

创建一个线程,Java 中创建线程,离不开一个关键的类,Thread。一种比较朴素的创建线程的方式,是写一个子类,继承 Thread,重写其中的 run 方法。

class MyTread extends Thread {    @Override    public void run() {        System.out.println("hello Thread!");    }}

这个 run 方法重写的目的,是为了明确咱们新创建出来的线程,要干啥活儿

光创建了这个类,还不算创建线程,还得创建实例,在主函数中写如下代码

Thread t = new MyTread();t.start();
  • System.out.println(“hello Thread!”); --> 先把新员工要做的任务安排好
  • Thread t = new MyTread(); --> 招聘进来一个新员工,把任务交给他
  • t.start(); --> 告诉新员工,你开始干活!

t.start(); --> 这才是真正开始创建线程(在操作系统内核中,创建出对应线程的 PCB,然后让这个 PCB 加入到系统链表中,参与调度)

Thread t = new MyTread();t.start();System.out.println("hello main!");

在这里插入图片描述
在这个代码中,虽然先启动的线程,后打印的 hello main,但是实际执行的时候,看到的却是先打印了 hello main 后打印了 hello thread。

  1. 每个线程是独立的执行流!main 对应的线程是一个执行流,MyThread 是另一个执行流,这两个执行流之间是并发(并发+并行)的执行关系!
  2. 此时两个线程执行的先后顺序,取决于操作系统调度具体实现(程序猿可以把这里的调度规则简单的视为是 “随机调度”)

因此执行的时候看到,是先打印 hello main 还是先打印 hello thread 是不能确定的。无论反复运行多少次看起来好像都是先打印 hello main,但是顺序仍然是不能确定的,当前看到的先打印 main,大概率是受到创建线程自身的开销影响的。

编写多线程代码的时候,一定要注意到!!!默认情况下,多个线程的执行顺序,是 “无序”,是 "随机调度"的。

我们还有一些手段能影响到线程执行的先后顺序的,但是调度器自身的行为修改不了,调度器仍然是随机调度,咱们最多是让某个线程先等待,等待另一个线程执行完了自己再执行

在这里插入图片描述

  • Process 进程
  • exit code 0 进程的退出码

操作系统中用 进程的退出码 来表示 “进程的运行结果”

  • 使用 0 表示进程执行完毕,结果正确
  • 使用非 0 表示进程执行完毕,结果不正确
  • 还有个情况 main 还没返回,进程就崩溃,此时返回的值很可能是一个随机值

要是我们不想要这个程序结束的这么快咋办?
就可以想办法让这个进程别结束这么快,好让咱们看下这里都有啥线程

while (true) {    System.out.println("hello Thread!");}
while (true){    System.out.println("hello main!");}

把他们都套上一个死循环之后看到打印结果是 hello main! 和 hello Thread! 交替打印

🥎2. 线程的查看

(注意不要关闭上述程序运行,查看线程需要)此时就可以查看当前 Java 进程里的线程有哪些(打开任务管理器可以观看到程序运行状态)

在这里插入图片描述

  1. 找出 JDK 安装的目录,找到 bin 文件里,在 JDK 里提供了 jconsole这样的工具,可以看到 Java 进程里的线程详情

在这里插入图片描述

  1. 点击运行(如果运行界面没有这样的选项,我们在打开的时候需要以管理员方式打开!)

在这里插入图片描述

  1. 点击进去即可(不用害怕hhh)

在这里插入图片描述

  1. 打开线程所在窗口

在这里插入图片描述

  1. 找到左下角,会有显示具体的线程

在这里插入图片描述

这个列表就是当前 Java 进程中的线程信息
其他的线程则是 JVM 中的辅助功能,有的线程负责垃圾回收,有的线程负责处理调试信息,比如咱们使用的 jconsole 能够连上这个 JVM ,也是靠对应的线程提供的服务

  1. 点击某个线程的时候,右侧会显示线程的详细情况

在这里插入图片描述

在这里插入图片描述

上面两张表里都描述了当前线程这里的各种方法之间调用的关联关系

这里的调用栈非常有用,未来调试一个 “卡死” 的程序的时候,就可以看下面每个线程的调用栈是啥,就可以初步的确认卡死的原因。

上面的死循环代码打印的太快太多,有时候我们不希望它这么快,不利于我们观察,于是用 sleep 来让线程适当休息一下

使用 Thread.sleep 的方式进行休眠,sleep 是Thread 的静态成员方法,sleep 的参数是一个时间,单位 ms

在这里插入图片描述

这个异常在多线程中经常能见到,interrupted 中断

通过 try-catch 抓捕异常后的运行结果

在这里插入图片描述

加上 sleep 之后,无序调度这个事情就被更加清楚的观察到了。

在这里插入图片描述

一个经典面试题:
谈谈 Thread 的 run 和 start 的区别:

  • 使用 start 可以看到两个线程并发的执行,两组打印交替出现
  • 使用 run 可以看到只是在打印 thread,没有打印 main
     

直接调用 run 并没有创建新的线程,而只是在之前的线程中,执行了 run 里的内容;使用 start,则是创建新的线程,新的线程里面会调用 run,新线程和旧线程之间是并发执行的关系

上述总的代码:

class MyTread extends Thread {    @Override    public void run() {        while (true) {            System.out.println("hello Thread!");            try {                Thread.sleep(1000);            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }}public class Demo1 {    public static void main(String[] args) {        //创建一个线程        //Java 中创建线程,离不开一个关键的类,Thread        //一种比较朴素的创建线程的方式,是写一个子类,继承 Thread,重写其中的 run 方法。        Thread t = new MyTread();        //t.start();        t.run();        while (true){            System.out.println("hello main!");            try {                Thread.sleep(1000);            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }}

 

🎶三. 创建线程的几种常见写法

  1. 创建一个类继承 Thread,重写 run。(上面已经写过)

这个写法,线程和任务内容是绑定在一起的!

  1. 创建一个类,实现 Runnable 接口,重写 run。
Runnable runnable = new MyRunnable();Thread t = new Thread(runnable);t.start();
  • 此处创建的 Runnable,相当于是定义了一个 "任务"(代码要干啥)
  • 需要 Thread 实例,把任务交给 Thread
  • 还是 Thread.start 来创建具体的线程
  • 这个写法,线程和任务是分离开的(更好的解耦合)(耦合的意思和表面差不多,模块 / 代码之间,关联关系越紧密,就认为耦合性越高,关联关系越不紧密,认为耦合性越低)
  • 写代码的时候追求 “高耦合” “低内聚”,让每个模块之间耦合性降低, 好处就是一个模块出了问题对另外的模块影响不大
  • 这种写法把任务内容和线程本身给分离开了,就把耦合性降低了(任务的本身和线程关系不大),假设这个任务不想通过多线程执行了,而是换成别的方式执行,这个时候代码的改动也不大。

一个问题:

为啥刚才使用 Thread,Runnable,interruptedException 都不需要 import ??啥样的类,不需要 import 就能直接使用??

  • 要么是同一个类中,要么这个类是在 java.lang 中(String)
  1. 仍然是使用继承 Thread 类,但是不再显式继承,而是使用 “匿名内部类”。
public class Demo3 {    public static void main(String[] args) {        Thread t = new Thread() {            @Override            public void run() {                while(true){                    System.out.println("hello Thread");                    try {                        Thread.sleep(1000);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                }            }        };        t.start();    }}
Thread t = new Thread() {

此处就是在创建了一个匿名内部类(没有名字),这个匿名内部类是 Thread 的子类,同时前面 new 关键字,就给这个匿名内部类创建出了一个实例

这一套操作实现了继承,方法重写,实例化

在 start 之前,线程只是准备好了,并没有真正被创建出来,执行了 start 方法,才真正在操作系统中创建了线程!!

Thread 实例是 Java 中对于线程的表示,实际上想要真正的跑起来,还是需要操作系统里面的线程,创建好了 Thread,此时系统里面还没有线程,直到调用 start 方法,操作系统才真的创建了线程(1.创建 PCB;2.把 PCB 加入到链表里)并进行运行。

  1. 使用 Runnable,是匿名内部类的方式使用
  • 方法一:
public class Demo4 {    public static void main(String[] args) {        Runnable runnable = new Runnable() {            @Override            public void run() {                while(true){                    System.out.println("hello thread");                    try {                        Thread.sleep(1000);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                }            }        };        Thread t = new Thread(runnable);        t.start();    }}
  • 方法二:(推荐)
public class Demo4 {    public static void main(String[] args) {Thread t = new Thread(new Runnable() {            @Override            public void run() {                while (true){                    System.out.println("hello Thread");                    try {                        Thread.sleep(1000);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                }            }        });        t.start();    }}

中间的匿名内部类的实例,是 Thread 的构造方法的参数!

  1. 使用 lambda 表达式,来定义任务。(推荐做法)
public class Demo5 {    public static void main(String[] args) {        Thread t = new Thread(() -> {            while (true){                System.out.println("hello Thread");                try {                    Thread.sleep(1000);                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        });        t.start();    }}

使用 lambda 表达式,其实是更简单的写法,也是推荐写法,形如 lambda 表达式这样的,能够简化代码编写的语法规则,称为 “语法糖”。

lambda 表达式本质上就仅仅是一个 “匿名函数”(没有名字的,只使用一次的函数)

实际上,线程还有其他的创建方式,后续介绍后面的创建方式!

  • 6.基于 Callable / FutureTask 的方式创建
  • 7.基于线程池的方式创建

至少有七种创建方式~

 

💦四. 多线程的优势

  1. 单个线程,串行的,完成 20 亿次自增。
public class Demo6 {    private static final long COUNT = 20_0000_0000;    private static void serial(){        //需要把方法执行的时间给记录下来        //记录当前的毫秒级时间戳        long beg = System.currentTimeMillis();        int a = 0;        for(long i = 0; i < COUNT; i++){            a++;        }        a = 0;        for(long i = 0; i < COUNT; i++){            a++;        }        long end = System.currentTimeMillis();        System.out.println("单线程消耗的时间:" + (end - beg) + " ms");    }    public static void main(String[] args) {        serial();    }}

运行结果:

在这里插入图片描述

使用一个线程串执行花了大约 1200 ms

  1. 两个线程,并发的,完成 20 亿次自增
public class Demo6 {    private static void concurrency(){        long beg = System.currentTimeMillis();        Thread t1 = new Thread(()->{            int a = 0;            for(long i = 0; i < COUNT; i++){                a++;            }        });        Thread t2 = new Thread(()->{            int a = 0;            for(long i = 0; i < COUNT; i++){                a++;            }        });        t1.start();        t2.start();        long end = System.currentTimeMillis();        System.out.println("并发执行的时间:" + (end - beg) + " ms");    }    public static void main(String[] args) {        concurrency();    }}

这个代码涉及到三个线程 t1,t2,main(调用 concurrency 方法的线程),三个线程都是并发执行的

t1.start(); t2.start(); 表示 t1,t2 是会开始执行的,同时,不等 t1,t2 执行完毕,main 线程就往下走了,于是就结束计时。此处的计时,是为了衡量 t1,t2 的执行时间,正确的做法应该是等到 t1,t2 都执行完,才停止计时。

在上述代码 t1.start(); t2.start(); 之后加上如下代码即可

try {    t1.join();    t2.join();} catch (InterruptedException e) {    e.printStackTrace();}

t1.join(); t2.join(); --> join 是等待线程结束(等待线程把自己的 run 方法执行完),在主线程中调用 t1.join,意思就是让 main 线程等待 t1 执行完,这两个 join 操作谁先谁后不影响。

运行结果:

在这里插入图片描述

如果没有 join 的限制,main 和 t1,t2 都是同时往下走的!多个线程并发执行的(同时在往下走),走的过程中,调度顺序不确定!调度在这个代码执行过程中不是只有一次,会有很多次,即使是上面的简单代码,也可能会出现成千上万次的调度!绝对不是说一个线程执行完了再调度给第二个线程!!!

上面的结果相较之下对比体现出两个线程并发执行确实快了不少,但是为什么两个线程并发执行不是单个线程串行执行的耗时一半呢?

  • 创建线程自身,也是有开销的
  • 两个线程在 CPU 上不一定是纯并行,也可能是并发(一部分时间并行了,一部分时间并发了)
  • 线程的调度,也是有开销的

 

❄️五. 多线程的使用场景

  1. 在 CPU 密集型

代码中大部分工作,都是在使用 CPU 进行运算(就像上面的反复++),使用多线程,就可以更好的利用 CPU 多核计算资源,从而提高效率!

  1. 在 IO 密集型场景

input output ,读写硬盘,读写网卡…这些操作都算 IO,像这些 IO 操作,几乎都是不消耗 CPU 就能完成快速读写数据的操作,既然 CPU 在摸鱼,就可以给他找点活儿干,也可以使用多线程,避免 CPU 过于闲置。

来源地址:https://blog.csdn.net/m0_67660672/article/details/129169677

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

多线程的初识和创建

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

多线程-线程的创建

线程的创建方式总结一下多线程的创建方式,多线程的实现一共四种方法,接下来将详谈一下创建的方式1、继承Thread类,而后覆写run()方法2、实现Runnable接口,而后覆写run()方法3、实现callable接口,而后覆写call方法4、线程池(后面专门
2016-10-19

Python多线程之线程创建和终止

python主要是通过thread和threading这两个模块来实现多线程支持。python的thread模块是比较底层的模块,python的threading模块是对thread做了一些封装,可以更加方便的被使用。但是python(cp
2023-01-31

Java多线程 - 创建线程池的方法 - ThreadPoolExecutor和Executors

文章目录 线程池(重点)线程池介绍实现线程池的方式方式一: 实现类ThreadPoolExecutorThreadPoolExecutor构造器的参数线程池处理Runnable任务线程池处理Callable任务 方式二:
2023-08-30

Java多线程的创建方式

这篇文章主要介绍“Java多线程的创建方式”,在日常操作中,相信很多人在Java多线程的创建方式问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Java多线程的创建方式”的疑惑有所帮助!接下来,请跟着小编一起来
2023-06-20

java怎么创建多线程

在Java中,有两种方法可以创建多线程:1. 继承`Thread`类:创建一个类,继承自`Thread`类,并重写`run()`方法,将线程执行的代码放在`run()`方法中。然后创建该类的实例,调用`start()`方法启动线程。```j
2023-08-12

Linux多线程怎么创建

在Linux中,可以使用pthread库来创建多线程。下面是一个简单的例子:```c#include #include // 线程函数void *thread_func(void *arg) {int thread_num = *((int
2023-08-16

Python中如何创建多线程?

这篇文章主要介绍了Python中如何创建多线程的相关资料,需要的朋友可以参考下
2023-01-06

C++ 函数库如何创建和使用多线程?

答案:在 c++++ 中,可以使用 std::thread 函数库创建和使用多线程以实现并发编程。详细描述:使用 std::thread 创建新线程,并在子线程中执行指定代码。使用同步机制(如互斥锁和条件变量)来确保线程安全地访问共享数据。
C++ 函数库如何创建和使用多线程?
2024-04-18

Java创建多线程服务器流程

这篇文章主要介绍了Java创建多线程服务器流程,以下实例演示了如何使用Socket类的accept()方法和ServerSocket类的MultiThreadServer(socketname)方法来实现多线程服务器程序
2023-05-18

Java中怎么创建多个线程

这篇文章给大家介绍Java中怎么创建多个线程,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。Java多线程代码如下package com.vista; class MyThread implements java.la
2023-06-17

如何在python中创建多线程

今天就跟大家聊聊有关如何在python中创建多线程,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。python可以做什么Python是一种编程语言,内置了许多有效的工具,Python几
2023-06-14

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录