首页 > 其他分享 >【多线程】多线程(1):概念,创建线程

【多线程】多线程(1):概念,创建线程

时间:2024-09-30 13:22:55浏览次数:12  
标签:run Thread 创建 线程 进程 多线程 cpu

【多线程的概念】

之前写过的所有代码,都只能使用“一个核心”,此时无论如何优化代码,最多只能用一个cpu核心,把这个核心跑满了,其他核心也是空闲着,通过写特殊的代码,把多个cpu核心都能应用起来,此为“并发编程”

之前使用的并发模式为“多进程编程”,其在创建/销毁进程时开销较大,一旦需求的场景需要频繁地创建/销毁进程,开销就十分明显了(最典型的就是服务器开发),为了解决这个问题,发明了“线程”(可以理解为更轻量的进程,也可以解决并发编程的问题,开销比进程低)

因此,多线程的编程成为了当前并发编程的主要模式

线程比进程更轻量,主要在于创建线程,省去了“分配资源”过程,销毁线程,省去了“释放资源”过程(一旦创建进程,同时也会创建第一个线程,就会负责分配资源,一旦后续创建第二个第三个等进程,就不必再重新分配资源了)

多进程:100只鸡分配到两个房间中,两个人在各自房间中吃鸡

多线程:100只鸡在一个房间中,两个人一起吃鸡(省去了再分配一个房间,再回收一个房间的操作)

【具体解析】

进程在系统中通过“PCB”这样的结构体来进行描述,通过“链表”这样的数据结构形式来组织的

而线程同样也通过“PCB”来描述(仅限于Linux)

一个进程,是一组PCB,一个线程,是一个PCB,两者之间是包含关系:一个进程中,可以包含多个线程,此时每个线程,都可以独立的到cpu上调度执行

线程是系统“调度执行”的基本单位

进程是系统“资源分配”的基本单位

【执行流程】

一个可执行程序运行时,操作系统会创建进程,给这个程序分配各种系统资源(cpu 内存,硬盘……),同时也会在这个进程中,创建一个或多个线程,这些线程再去cpu上调度执行

//如果有多个线程在一个进程中,每个线程都会有自己的状态,优先级,上下文,记账信息,每个都会各自独立的在cpu上调度执行

//进程包含线程,而包含,要么是包含一个,要么多个,不能没有(进程中不能没有线程)

【注意】

能够提高效率,关键是充分利用多核心进行“并发执行”,如果只是“微观并发”,速度不会提升,真正能提升速度的是“并行”,凡事有度,尽管线程越多处理越快,但如果线程数目太多,比如超出了cpu核心数目,此时就无法在微观上完成所有线程的“并行”执行,势必存在严重的“竞争”,可能会影响效率

//n个人吃一堆鸡,人如果太多,那就有人吃不到

【线程安全问题】

同一个进程中的线程之间,共用的是同一份资源(内存资源,指代码中定义的变量/对象),如果多个线程针对同一个变量进行读写操作(尤其是写操作),就容易发生冲突,一旦发生冲突,就可能使程序出现问题,此为“线程安全问题”

//两个人看上去同一只鸡,开始争抢,打起来了

【异常与崩溃】

当一个进程中有多个线程时,一个某个线程抛出异常,此时如果能妥善处理那还好,一旦处理不当可能导致整个进程都崩溃,因此其他线程会随之崩溃

但多个进程之间,一般不会相互影响,这一点也称为“进程的隔离性”

//一个人突然犯病,于是想掀桌,其他人来镇压,镇压成功无事发生,镇压失败则掀桌,所有人吃不了鸡

【注意2】

核心,cpu

“核心”是cpu的核心,是硬件设备

如果把cpu当成工厂,cpu的核心就是干活工人,核心数量越多,干活工人越多

进程

进程是系统管理的软件资源,进程也叫“任务”,这些任务要交给这些核心来进行

【总结:进程和线程的概念区别】(重点掌握)

1.进程包含线程

一个进程中可以有一个线程,也可以有多个线程,不能没有线程

2.线程是系统“调度执行”的基本单位,进程是系统“资源分配”的基本单位

3.同一个进程里的线程之间,共用同一份系统资源(内存,硬盘,网络带宽等),尤其是“内存资源”,就是代码中定义的变量/对象,编程中,多个线程可以共用同一份变量

4.线程是当下实现并发编程的主流方式,通过多线程,可以充分利用好多核CPU,但也不是线程数目越多就越好,线程数量达到一定程度,把多个核心都利用充分后,此时继续增加线程,无法再提高效率,甚至可能会影响效率(线程调度,也是有开销的)

5.多个线程之间共享资源,共享意味着可能会相互影响,线程安全问题中,一个线程抛出异常,可能会带走其他线程

6.多个进程之间一般不会相互影响,一个进程崩溃了也不会影响到其他进程,即“进程的隔离性”

【在Java中编写多线程程序】

线程本身是操作系统提供的概念,操作系统提供API,供程序员调用,而Java(JVM)把这些系统API封装好了,因此无需关心原生API,只需要了解Java提供的这套API即可

class MyThread extends Thread
{
    
}

此处Thread类可以直接使用,而无需导包(这个类在Java.lang这个包中,Java.lang中的类都是默认无需导包的)

【创建线程】

继承Thread类不是主要目的,最主要的目的是重写Thread类中的run方法,在run方法中的代码,就是即将创建出的线程要执行的逻辑

class MyThread extends Thread
{
    public void run()
    {
        System.out.println("hello world");
    }
}

class Demo
{
    public static void main(String[] args)
    {
        MyThread t = new MyThread();//真正的创建线程
        t.start();
    }
}

这是一个最简单的多线程代码,它分为两部分

第一部分:MyThread继承父类Thread,并重写其内部的run方法,第二部分

第二部分:在main方法中创建MyThread类的实例,再使用它提供的start方法来去创建线程

调用start就会在进程内部创建出一个新的线程,新的线程就会去执行刚才run方法中的代码

注意:像run这种,用户手动定义了,但是没有手动调用,最终这个方法被系统/库/框架在合适的时机进行自行调用,此时这样的方法就称为“回调函数”

这个代码执行起来是一个进程,但这个进程中包含了两个线程,其中调用main方法会自动创建一个线程,这个线程为“主线程”,一个进程中至少要有一个线程,至少的这一个线程,就是主线程

t.start();

而这条代码相当于创建了一个新的线程,主线程和新线程会并发/并行的在cpu上执行

【并发执行】

class MyThread extends Thread
{
    public void run()
    {
        while(true) {
            System.out.println("hello A");
        }
    }
}

class Demo
{
    public static void main(String[] args)
    {
        MyThread t = new MyThread();//真正的创建线程
        t.start();
        
        while(true){
            System.out.println("hello B");
        }
    }
}

执行该代码后,可以看到在控制台中高频快速地进行二者输出的交叉式打印,这就是“并发进行”

【休眠操作】

这种短时间内高频快速地进行二者输出的交叉式打印对机器会造成大量负荷,因此在循环中可以加上休眠操作,让循环每循环一次都休息一会儿,避免cpu消耗过大

这是一个静态方法,需要通过类名Thread去调用

 public void run()
    {
        while(true) {
            System.out.println("hello A");
            try {
                Thread.sleep(1000);//休眠操作
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

class Demo
{
    public static void main(String[] args)
    {
        MyThread t = new MyThread();//真正的创建线程
        t.start();

        while(true){
            System.out.println("hello B");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

这里处理异常只能try/catch而不能throw的原因是:这个代码在重写的run方法内部,父类中的run没有throws这样的异常声明,因此子类重写时不能增加throws,只能try/catch

多线程之间,谁先去CPU上调度执行,这个过程是“不确定的”,不是数学意义上的随机,而是这个调度顺序取决于操作系统内核中“调度器”的实现,调度器中内有一套规则,但程序员无法干预也无法感受,只能把这个过程视为 “随机”

【创建线程其他的写法】

【Runnable】

实现Runnable,重写run

Runnable是一个接口,因此需要用到interface,以实现的方式进行表述

class MyRunnable implements Runnable
{
    public void run()
    {
        while(true) {
            System.out.println("hello A");
                Thread.sleep(1000);
        }
    }
}

public class Demo2
{
    public static void main(String[] args) {
        MyRunnable runnable = new MyRunnable();
        Thread t = new Thread(runnable);
        t.start();

        while(true){
            System.out.println("hello B");
                Thread.sleep(1000);     
        }
    }
}

Runnable用来描述要执行的任务是什么,通过Thread创建线程,线程要描述的任务是通过Runnable来描述的,而不是通过Thread自己来描述的,其与第一种方式的区别就在这里

【匿名内部类】


    Thread t = new Thread()//定义匿名内部类,这个类是Threaad类的子类
    {
        public void run()
        {

        }
    };

本质上和第一种方法一样,继承Thread,重写run,通过匿名内部类来实现

匿名内部类,一般就是“一次性使用”的类,用完就丢,但是很方便

【基于匿名内部类的方式】

 Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                while(true){
                    System.out.println("hello B");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        );

这个写法,在Thread这个构造方法中直接写new Runnable,去定义匿名内部类,整体作为Thread方法的参数

【基于lambda的方式】

 Thread t = new Thread(() ->{
            while(true){
                System.out.println("hello B");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

【Thread类其他的属性和方法】

给线程命名的方法,不起名字则默认叫做Thread-0,Thread-1……

【线程的属性】

ID,名称,状态,优先级:JVM自动分配,不能手动设置

状态:java中把线程的状态,分为阻塞与就绪

优先级:设置不同的优先级,会影响到系统的调度,这里的影响是基于“统计”规则的影响,直接肉眼观察,很难观察到结果

【前台,后台线程】

如果这个线程在执行过程中,能够阻止进程结束此时这个线程就是“前台线程”

如果这个线程在执行过程中,不能阻止进程结束(虽然线程在执行着,但是进程要结束了,此时这个线程也会随之被带走),这样的线程就是“后台线程”

【前台,后台线程】

如果这个线程在执行过程中,能够阻止进程结束,此时这个线程就是“前台线程”

如果这个线程在执行过程中,不能阻止进程结束(虽然线程在执行着,但是进程要结束了,此时这个线程也会随之被带走),这样的线程就是“后台线程”

举个例子:

上述过程中,主持人决定了这场宴席什么时候结束,当他宣布结束时,宴席就散了,若“我”是个只会干饭的小透明,无法决定宴席何时结束,在主持人还没有宣布宴席结束时溜了也不影响宴席继续

那么“我”就是“后台线程”

1.进程要结束,(前台线程要结束),后台线程无力阻止

2.后台线程先结束了,也不影响进程的结束

主持人就是“前台线程”

1.前台线程宣布结束,此时进程就结束,后台线程也会随之结束

2.前台线程不结束,后台线程结束了也不影响

【守护线程】

标签:run,Thread,创建,线程,进程,多线程,cpu
From: https://blog.csdn.net/2301_81305165/article/details/142464463

相关文章

  • 虚拟磁盘的创建、分区与分离
            在取证学习中,实验过程需要各种硬盘格式或者文件系统时,可以用虚拟磁盘来进行。有时候分区太大导致操作很慢,或者不希望正常使用的分区数据受影响,都可以用虚拟磁盘来帮助完成实验。        虚拟磁盘:把硬盘里的一部分空间以文件的形式申请、占用下来,当作......
  • 今日一学,sql优化,创建索引的优缺点
    收藏了,但是不打开,久而久之就忘了,今日一学!所谓是好记性不如烂键盘。**2024Javaoffer收割指南**sql优化尽量避免使用select*,返回无用的字段会降低效率。优化方式:只能使用具体的字段代替select具体字段,只返回使用到的字段。(虽然我经常select*但是一个表字段非常多,那......
  • c语言实现:链表创建、插入、删除、翻转
    #include<stdio.h>#include<stdlib.h>//链表创建typedefstructNode{intdata;structNode*next;}Node;//创建一个节点Node*createNode(intdata){Node*newNode=(Node*)malloc(sizeof(Node));newNode->data=data;newNode......
  • 【0333】Postgres内核之 auxiliary process(辅助进程)创建 PGPROC
    1.auxiliaryprocess当我们是辅助进程(auxiliaryprocess)时,不会进行完整的InitPostgres初始化操作,但即使在辅助进程中,也有几件事需要被启动。这里第一件就是“创建一个PGPROC,以便我们能够使用LWLocks。在EXEC_BACKEND情形下,这一操作已由SubPostmasterMain()完......
  • conda创建并切换python虚拟环境
    项目的不同模块可能需要用到一个python库的不同版本,为避免冲突,需要通过conda构建多个python虚拟环境,来安装不同版本的库condaenvlist#列出所有环境condacreate--namepy310#创建环境py310condaactivatepy310#切换到该环境condainstallpython=3.10#安装python......
  • c++线程--快速上手
    线程创建头文件#includethread是在C++11标准中引入的。C++11标准引入了对多线程编程的标准化支持,其中包括了线程的创建、管理和同步机制。头文件提供了基本的线程支持库,允许开发者直接使用c++线程进行并行编程,而无需依赖操作系统特定的API#include<iostream>#include......
  • 华三设备的用户创建、ssh、telnet等的配置
    sshserverenabletelnetserverenablepublic-keylocalcreatersapublic-keylocalcreatedsauser-interfacevty04authentication-modeschemeprotocolinboundsshqulocal-usertonyclassmanagepasswordsimpleroot#12345service-typeterminalsshteln......
  • 【问题解决】win10日志错误:创建 TLS 客户端凭据时发生致命错误。 内部错误状态为 1001
    背景最近win10死机了一次,查看事件管理器发现有大量的报错:“创建TLS客户端凭据时发生致命错误。内部错误状态为10013”,如图:解决win键搜索internet选项确定。原因参考错误:“创建TLS客户端凭据时发生致命错误。内部错误状态为10013”的说法是win10对TLSv3.0兼容性......
  • C#实现多线程的几种方式
    前言多线程是C#中一个重要的概念,多线程指的是在同一进程中同时运行多个线程的机制。多线程适用于需要提高系统并发性、吞吐量和响应速度的场景,可以充分利用多核处理器和系统资源,提高应用程序的性能和效率。多线程常用场景CPU密集型任务.I/O密集型任务.并发请求处理.大数......
  • 线程的数量应当与处理器的数量的关系
    线程的数量应当与处理器的数量相匹配,否则可能会导致性能的下降而不是提升,尤其是在只有一个处理器的情况下。线程是程序执行的最小单位,它可以在不同的处理器核心上并发执行,利用多核处理器的能力来提升计算效率。处理器(CPU)核心是物理上能够执行指令的单元。一个处理器可以有一个......