多线程编程1

news2025/1/12 13:40:49

一、线程的引入

上节,我们介绍了进程的概念,以及操作系统内核是如何管理进程的(描述+组织),PCB中的核心属性有哪些,

引入进程这个概念,最主要的目的,就是为了解决“并发编程”这样的问题。

为什么我们需要并发编程?

这是因为CPU进入了多核心时代,为了进一步提高程序的执行速度,就需要充分利用CPU的多核资源这就需要“并发编程”。

多进程编程让大量进程可以在多个CPU核心上运行,程序代码已经能够把CPU的多核资源利用起来了,已经可以解决“并发编程”的问题了。

我们为什么又要学习多线程编程呢?

线程又是什么呢?和进程又有什么关系?

带着这些疑问,我慢慢进行介绍。

二、线程和进程的关系

(1)进程包含线程。一个进程可以包含一个线程,也可以包含多个线程。进程中至少有一个线程,不能没有。

(2)一个线程是通过一个PCB来描述的,PCB对应的是线程,一个线程对应一个PCB,一个进程对应1个或多个PCB。

(3)进程是操作系统资源分配的基本单位,线程是操作系统调度执行的基本单位。同一进程里的多个线程之间共享进程资源。

PCB中的核心属性:pid、内存指针、文件描述符表,这些是进程中的线程共用的处于同一个进程中的线程,pid相同,内存指针和文件描述符表也是一样的。

PCB中与调度相关的属性:状态、优先级、上下文、记账信息,是每个线程自己有自己的,各自记录各自的。

对于第三点,我再啰嗦几句,希望能加深大家的理解:

同一个进程里的多个线程之间,共用了进程的同一份资源这里共用的资源主要指的是内存指针和文件描述符表。比如:线程1里new的对象,在线程2,3,4里都可以直接使用(共用内存指针),线程1打开的文件,在线程2,3,4里都可以直接使用(共用文件描述符表)。

操作系统,实际调度的时候,以线程为单位进行调度的。并不关系进程,只关心线程。谈到调度,就和进程无关了!!上节提到的进程调度,指的是这里的进程只包含一个线程的情况。如果进程中有多个线程,那么每个线程是独立在CPU上调度的。比如,线程1可能在核心A上执行,线程2可能在核心B上执行。线程是操作系统调度执行的基本单位。每个线程都有自己的执行逻辑,我们称为执行流。

三、为什么要使用多线程编程?

多进程和多线程都可以解决并发编程问题,为什么更倾向于使用多线程编程呢?

因为,

进程,太“重”了。创建/销毁/调度一个进程,都需要很大的资源消耗,速度慢。

线程,又叫“轻量级进程”。创建/销毁/调度一个线程,消耗资源少,速度快。

使用多线程编程,会提高效率。

为什么线程比较“轻”?

因为只有在创建第一个线程时,操作系统会进行资源分配(主要指内存指针,文件描述符表),之后创建的第2,3,4...个线程,复用之前的分配的资源,基本不需要操作系统再分配资源。销毁一个线程,也基本不需要释放资源。除非这个进程中只有这一个线程了,才需要释放资源。

而进程就不一样了,创建一个进程,操作系统会进行资源分配,销毁一个进程,要释放资源。创建第二个进程,还需要申请资源,销毁第二个进进程,同样需要释放资源。消耗资源多,速度慢。

因此,由于进程和线程各自的特点,我们一般使用多线程编程,减少资源消耗,提高速度。

四、线程越多越好?多线程会有安全问题吗?

增加线程数量并不是可以一直提高速度。 CPU的核心数量是有限的。线程太多,核心数目有限,不少的开销反而浪费在线程调度上。所以,并不是线程越多越好。

多线程容易出现线程安全问题。

(1)多线程中,共享同一份资源,可能会出现多个线程同时都需要同一个资源的现象(如同一个变量),出现争抢

(2)如果一个线程抛异常,处理不好的话,可能会把整个进程都带走,其它线程也就挂了。

什么时候会出现安全问题多个执行流访问同一个共享资源的时候。

线程模型,天然就是资源共享的,多线程争抢同一个资源(如同一个变量)非常容易触发。

进程模型,天然就是资源隔离的,不容易触发。只有进行进程间通信时,多个进程访问同一个资源,这时才可能会出问题。

也就是说,多线程会提高效率,但是不如多进程安全。当然,代码写的靠谱,线程安全问题也不怕

五、在Java中如何进行多线程编程?

操作系统提供了操作线程的一系列API。

而Java是一个跨平台的语言,很多操作系统提供的功能,都被JVM给封装好了。我们不需要学习操作系统提供的原生API,只需要学习Java提供的API就行啦

Java操作多线程,最核心的类是Thread(在java.lang下,不用import)

创建线程,是希望线程成为一个独立的执行流,能执行一段代码。我们可以这样理解,

创建线程,就相当于雇了个人来干活,我们得告诉他要干啥活,他才能去执行。

如何告诉他要干啥活呢?

我们有如下方法,java中创建线程的写法有很多种,如下所示:

  1. 继承Thread,重写run方法
  2. 实现Runnable接口
  3. 使用匿名内部类,继承Thread
  4. 使用匿名内部类,实现Runnable接口
  5. 使用Lambda表达式

下面分别进行介绍:

1、继承Thread,重写run方法

class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println("hello thread");
    }
}
public class ThreadDemo1 {
    public static void main(String[] args) {
        Thread t = new MyThread();
        t.start();
    }
}

t.start();

start方法是线程中的一个特殊方法作用是创建一个线程。

在上面代码中start这个方法创建了一个新的线程,新的线程负责执行run方法并不是start方法里面调用了run方法,是新的线程调用的run方法当run方法执行完时,新的线程自然销毁。

那么start是如何创建一个新线程的?

调用操作系统提供的API(系统调用),通过操作系统内核创建新线程的PCB,并且把要执行的指令交给这个PCB,当PCB被调度到CPU上执行的时候,也就执行到了线程run方法中的代码了。

在main方法中直接打印hello world,和在main方法中调用start方法的上述做法有啥区别?

如果只是在main方法中直接打印hello world,这个java进程就只有一个线程(调用main方法的线程),也就是主线程。

在main方法中调用t.start(),是主线程调用start方法创建出一个新的线程,新的线程调用run方法执行其中的代码。这个java进程中有两个线程。

如果把 t.start(); 改成 t.run(); 有什么区别吗?

有很大区别。

t.run(); 的话,这个java进程中还是只有一个主线程。所有的活都是主线程一个人干的。因为new Thread对象的操作并不创建线程,只有调用了start方法才是真正创建了PCB,才真正有个货真价实的线程。

2、实现Runnable接口

解耦合,目的是让线程线程要干的活之间分离开

未来如果要改代码,不用多线程了,使用多进程,或者线程池,协程......此时代码改动比较小。

//Runnable 作用:描述一个”要执行的任务“, run方法就是执行任务的细节
class MyRunnable implements Runnable{

    @Override
    public void run() {
        System.out.println("hello world");
    }
}
public class ThreadDemo2 {
    public static void main(String[] args) {
        //这只是描述了个任务,就是线程要干的活
        Runnable runnable = new MyRunnable();
        //把任务交给线程来执行
        Thread t = new Thread(runnable);
        t.start();
    }
}

3、使用匿名内部类,继承Thread

public class ThreadDemo3 {
    public static void main(String[] args) {
        Thread t = new Thread(){
            @Override
            public void run() {
                System.out.println("hello world");
            }
        };
        t.start();
    }
}

 

红框框里是一个匿名内部类对象,这里做了两件事:

1、创建了一个Thread的子类,继承Thread类,(子类没有名字,所以才叫“匿名”)。

2、对子类进行实例化(new),让 t 引用指向该实例。

4、使用匿名内部类,实现Runnable接口

public class ThreadDemo4 {
    public static void main(String[] args) {
        Thread t = new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("hello world");
                    }
                }
        );
        t.start();
    }
}

5、使用Lambda表达式

直接把 lambda 传给 Thread 的构造方法

lambda 就是个匿名方法

public class ThreadDemo5 {
    public static void main(String[] args) {
        Thread t = new Thread(
                () -> {
                    System.out.println("hello world");
                }
        );
        t.start();
    }
}

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

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

相关文章

机器学习周记(第二十六周:文献阅读-DPGCN)2024.1.15~2024.1.21

目录 摘要 ABSTRACT 1 论文信息 1.1 论文标题 1.2 论文摘要 1.3 论文背景 2 论文模型 2.1 问题描述 2.2 论文模型 2.2.1 时间感知离散图结构估计(Time-aware Discrete Graph Structure Estimation Module,TADG Module) 2.2.2 时间…

【Linux】grub命令行引导进入系统

文章目录 1.grub命令行界面2.设置启动目录3.chainloader加载windows启动文件4.启动5.grub命令行无响应办法 在卸载Linux系统后,有的小白可能会忘记删除Linux的EFI引导。这样的话,下次开机时就会自动进入grub的命令行,连windows系统都进不去了…

C++入门学习(八)sizeof关键字

sizeof 是 C 和 C 中的一个运算符&#xff0c;用于确定特定类型或对象的内存大小&#xff08;以字节为单位&#xff09;。 1、查看数据类型占据内存大小 #include <iostream> using namespace std; int main() {short a 1;int b 1;long c 1;long long d 1;cout<…

Dubbo 的心脏:理解和应用多种协议【十三】

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 Dubbo 的心脏&#xff1a;理解和应用多种协议【十三】 前言<dubbo:protocol> 基础<dubbo:protocol> 的定义和作用微服务中协议的重要性支持的协议类型配置示例 配置基本配置参数高级配置选…

配置DNS主从服务器,实现真反向解析

主服务器 [rootbogon ~]# systemctl stop firewalld.service #关闭防火墙 [rootbogon ~]# setenforce 0 #关闭selinux [rootbogon ~]# systemctl restart named #启动dns服务 [rootbogon ~]# vim /etc/named.conf #进入dns配置文件 options {#监听…

2024年开年的荣誉--来自国产数据库

上周在北京参加了阿里云的开发者大会&#xff0c;我因为去年做了一点小贡献。非常荣幸的获得了阿里云的MVP的这个殊荣。&#xff08;期间也认识了一些大神级的人物&#xff09;还有就是一些网上认识的打卡们线下见面。 这个也是我一直追求的荣誉。 几乎在同时P&#xff08;Ping…

力扣hot100 找到字符串中所有字母异位词 滑动窗口 双指针 一题双解

Problem: 438. 找到字符串中所有字母异位词 文章目录 思路滑动窗口 数组滑动窗口 双指针 思路 &#x1f469;‍&#x1f3eb; 参考题解 滑动窗口 数组 ⏰ 时间复杂度: O ( n ) O(n) O(n) &#x1f30e; 空间复杂度: O ( 1 ) O(1) O(1) class Solution { // 滑动窗口 …

基于STM32CubeMX创建FreeRTOS—以STM32F429为例

目录 1. 实验任务 2. 使用STM32CubeMX创建基础工程 2.1 打开STM32CubeMX 2.2 创建新项目 2.3 时钟设置 2.5 修改时钟基准&#xff0c;打开串行调试 2.6 配置串口 2.7 配置状态指示灯 2.8 FreeRTOS 2.9 配置工程输出项 3. 代码编辑 3.1 printf重映射 3.1.1 使用ARM…

资产及价值导入

文章目录 1 Introduction2 Code3 Summary 1 Introduction We will implement the following fuction for importing asset value . In the code we introduce that how to transfer value for BAPI. 2 Code DATA: key TYPE bapi1022_key,generaldata …

ROS第 13 课 TF 坐标系广播与监听的编程 实现

文章目录 第 13 课 TF 坐标系广播与监听的编程 实现1.机器人的坐标变换2.创建功能包3.编程方法3.1 编写广播和监听程序3.2 运行程序 第 13 课 TF 坐标系广播与监听的编程 实现 1.机器人的坐标变换 在进行编程前&#xff0c;先需要了解机器人的坐标变换。这里以运行海龟案例来…

洛谷-P1002-[NOIP2002 普及组]-过河卒

[NOIP2002 普及组] 过河卒 题目描述 棋盘上 A A A 点有一个过河卒&#xff0c;需要走到目标 B B B 点。卒行走的规则&#xff1a;可以向下、或者向右。同时在棋盘上 C C C 点有一个对方的马&#xff0c;该马所在的点和所有跳跃一步可达的点称为对方马的控制点。因此称之为…

代码随想录第十八天 513 找树左下角的值 112 路径之和 106 从中序与后序遍历序列构造二叉树

LeetCode 513 找树左下角的值 题目描述 给定一个二叉树的 根节点 root&#xff0c;请找出该二叉树的 最底层 最左边 节点的值。 假设二叉树中至少有一个节点。 示例 1: 输入: root [2,1,3] 输出: 1示例 2: 输入: [1,2,3,4,null,5,6,null,null,7] 输出: 7 思路 1.确定递…

多级缓存

一、多级缓存 传统的缓存策略一般是请求到达Tomcat后&#xff0c;先查询Redis&#xff0c;如果未命中则查询数据库&#xff0c;如图&#xff1a; 存在下面的问题&#xff1a; •请求要经过Tomcat处理&#xff0c;Tomcat的性能成为整个系统的瓶颈 •Redis缓存失效时&#xff…

打折:阿里云国外服务器价格购买优惠活动

阿里云国外服务器优惠活动「全球云服务器精选特惠」&#xff0c;国外服务器租用价格24元一个月起&#xff0c;免备案适合搭建网站&#xff0c;部署独立站等业务场景&#xff0c;阿里云服务器网aliyunfuwuqi.com分享阿里云国外服务器优惠活动&#xff1a; 全球云服务器精选特惠…

77. 组合 - 力扣(LeetCode)

题目描述 给定两个整数 n 和 k&#xff0c;返回范围 [1, n] 中所有可能的 k 个数的组合。 你可以按 任何顺序 返回答案。 输入示例 n 4, k 2输出示例 [[2,4],[3,4],[2,3],[1,2],[1,3],[1,4], ]解题思路 我们使用回溯、深度优先遍历的思想&#xff0c;我们使用一个栈 path…

linux驱动(八):block,net

本文主要探讨210的block驱动和net驱动。 block 随机存取设备且读写是按块进行,缓冲区用于暂存数据,达条件后一次性写入设备或读到缓冲区 块设备与字符设备:同一设备支持块和字符访问策略,块设备驱动层支持缓冲区,字符设备驱动层没有缓冲 块设备单位:扇…

Leetcode的AC指南 —— 栈与队列:20. 有效的括号

摘要&#xff1a; **Leetcode的AC指南 —— 栈与队列&#xff1a;20. 有效的括号 **。题目介绍&#xff1a;给定一个只包括 ‘(’&#xff0c;‘)’&#xff0c;‘{’&#xff0c;‘}’&#xff0c;‘[’&#xff0c;‘]’ 的字符串 s &#xff0c;判断字符串是否有效。 有效字…

基于JavaWeb+SSM+Vue智能社区服务小程序系统的设计和实现

基于JavaWebSSMVue智能社区服务小程序系统的设计和实现 滑到文末获取源码Lun文目录前言主要技术系统设计功能截图订阅经典源码专栏Java项目精品实战案例《500套》 源码获取 滑到文末获取源码 Lun文目录 目录 1系统概述 1 1.1 研究背景 1 1.2研究目的 1 1.3系统设计思想 1 2相…

【计算机硬件】4、练习题

根据冯诺依曼原理&#xff0c;计算机硬件的基本组成是&#xff08;&#xff09; A.输入、输出设备、运算器、控制器、存储器 B.磁盘、软盘、内存、CPU、显示 C.打印机、触摸屏、键盘、软盘 D.鼠标、打印机、主机、显示器、存储器 CPU执行算术运算或者逻辑运算时&#xff0c;常…

class_14:继承

C继承有点类似于c语言 结构体套用 #include <iostream> #include <string> using namespace std;//基类,父类 class Vehicle{ public:string type;string contry;string color;double price;int numOfWheel;void run();void stop(); };//派生类&#xff0c…