多线程编程1

一、线程的引入

上节,我们介绍了进程的概念,以及操作系统内核是如何管理进程的(描述+组织),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{@Overridepublic 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{@Overridepublic 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(){@Overridepublic 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() {@Overridepublic 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.mzph.cn/news/638397.shtml

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

相关文章

JavaScript语法摘要

JavaScript语法摘要 JavaScript语法通过各种规则和组合,就能创建出丰富多彩的程序呢!它包括了怎么声明和使用变量、如何定义和赋值,还有怎么用运算符和表达式等等。另外,我还发现了一些有趣的概念,比如关键字、注释、…

Python学习从0到1 day7 Python判断语句

路远殊途,祝你得偿所愿 ——24.1.21 前言 进行逻辑判断,是生活中常见的行为,同样,在程序中,进行逻辑判断也是最为基础的功能 一、布尔类型和比较运算符 1.布尔类型 进行判断,有两个结果,True、…

springboot集成COS对象存储

1.申请腾讯云存储桶 新建密钥(后面配置要用到) 2.编写工具类 此处使用工具类进行基本属性配置,也可选择在yml中配置 package com.sfy.util;import com.qcloud.cos.COSClient; import com.qcloud.cos.ClientConfig; import com.qcloud.cos.a…

基于xgboost-LGBM-SVM的病人哮喘病识别检测 数据+代码

基于xgboost-LGBM-SVM的病人哮喘病识别检测-完整代码可直接运行_哔哩哔哩_bilibili 代码: from sklearn import preprocessing import random from sklearn.model_selection import train_test_split from sklearn.preprocessing import MinMaxScaler from sklearn import pr…

声明式注解对XXL-JOB的定时任务代码生效吗?

说明:源于博主的思考,本文验证一下声明式注解,即Transactional注解,对XXL-JOB的定时任务是否生效。 准备 首先,创建一个需要事务的场景。有两张表,一张部门表,一张用户表,用户隶属…

基于YOLOv8的目标识别、计数、电子围栏的项目开发过程

0 前言 用于生产环境中物体检测、识别、跟踪,人、车流量统计,越界安全识别 1 YOLOv8概述 YOLOv8 是Ultralytics的YOLO的最新版本。作为一种前沿、最先进(SOTA)的模型,YOLOv8在之前版本的成功基础上引入了新功能和改进,以提高性…

cupy,一个超级实用的 Python 库!

更多资料获取 📚 个人网站:ipengtao.com 大家好,今天为大家分享一个超级实用的 Python 库 - cupy。 Github地址:https://github.com/cupy/cupy 深度学习和科学计算需要处理大规模的数据和复杂的计算任务,而Python是一…

tag 标签

tag 标签 在使用 Git 版本控制的过程中,会产生大量的版本。如果我们想对某些重要版本进行记录,就可以给仓库历史中的某一个commit 打上标签,用于标识。 在本章中,我们将会学习如何列出已有的标签、如何创建和删除新的标签、以及…

20240116使用Firefly的AIO-3399J的预编译的Android10固件确认RT5640声卡信息

20240116使用Firefly的AIO-3399J的预编译的Android10固件确认RT5640声卡信息 2024/1/16 17:55 百度:RK3399 ALC5640 RK3399 RT5640 BING:RK3399 ALC5640 LINE-IN接麦克风不会有声音的。 耳机只有右边有声音,但是偏小,可以通过音量…

C++ memmove 学习

memmove&#xff0c;将num字节的值从源指向的位置复制到目标指向的内存块。 允许目标和源有重叠。 当目标区域与源区域没有重叠则和memcpy函数功能相同。 宽字符版本是wmemmove&#xff0c;安全版本加_s&#xff1b; #include "stdafx.h" #include<iostream&g…

如何为不同品牌的笔记本电脑设置充电限制,这里提供详细步骤

笔记本电脑的电池健康状况至关重要。延长电池寿命可以帮你省下不少钱。对于长时间充电的Windows 11笔记本电脑用户来说,将电池电量限制在80%是很重要的。 虽然操作系统没有提供设置自定义电池充电限制的内置方法,但仍有一些方法可以在Windows 11中配置电池设置,并将其限制在…

华南理工大学数字信号处理实验实验一(薛y老师版本)matlab源码

一、实验目的 1、加深对离散信号频谱分析的理解&#xff1b; 2、分析不同加窗长度对信号频谱的影响&#xff1b; 3、理解频率分辨率的概念&#xff0c;并分析其对频谱的 影响&#xff1b; 4、窗长和补零对DFT的影响 实验源码&#xff1a; 第一题&#xff1a; % 定义离散信…

【多线程的安全问题】synchronized 和 volatile——你必须知道的妙用!

&#x1f4c4;前言&#xff1a;本文的主要内容是讨论个人在多线程编程带来的安全问题的表现、原因以及对应的解决方法。 文章目录 一. 了解多线程安全问题二. 线程不安全的现象及原因&#x1f346;1. 修改共享的数据&#xff08;根本原因&#xff09;&#x1f345;2. 原子性&am…

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…

commit 历史版本记录修正

commit 历史版本记录修正 当 Bug 发生的时候&#xff0c;我们会需要去追踪特定 bug 的历史记录&#xff0c;以查出该 bug 真正发生的原因&#xff0c;这个时候就是版本控制带来最大价值的时候。 因此&#xff0c;要怎样维持一个好的版本记录是非常重要的&#xff0c;下面是一…

Rockchip linux USB 驱动开发

Linux USB 驱动架构 Linux USB 协议栈是一个分层的架构&#xff0c;如下图 5-1 所示&#xff0c;左边是 USB Device 驱动&#xff0c;右边是 USB Host 驱动&#xff0c;最底层是 Rockchip 系列芯片不同 USB 控制器和 PHY 的驱动。 Linux USB 驱动架构 USB PHY 驱动开发 USB 2…

手机也能随时随地玩红警啦!

还在为找不到红警安装包苦恼吗&#xff1f; 现在可以随时随地&#xff0c;无论手机、ipad、电脑都可以无需安装包在线玩红警啦&#xff01;&#xff01; 不仅能本地单机玩耍&#xff0c;还能联网玩耍&#xff08;可以和老外一起玩哦&#xff5e;&#xff09; 具体在线链接可…

算法第二十一天-丑数

丑数 题目要求 解题思路 首先判断数字是不是为0或者负数&#xff0c;两者均不可能成为丑数&#xff1b; 之后对n进行不断整除&#xff0c;直到无法除尽为止。 简单判断最后的数是不是1即可。 代码 class Solution:def isUgly(self, n: int) -> bool:if n<0:return Fa…

Linux启动流程梳理值得收藏

Linux启动流程总的来说可以分成三个阶段 Linux启动流程图 第一步&#xff1a;上电 在 x86 系统中&#xff0c;将 1M 空间最上面的 0xF0000 到 0xFFFFF 这 64K 映射给 ROM。 当电脑刚加电的时候&#xff0c;会做一些重置的工作&#xff0c;将 CS 设置为 0xFFFF&#xff0c;将 IP…

Docker命令---搜索镜像

介绍 使用docker命令搜索镜像。 命令 docker search 镜像命令:版本号示例 以搜索ElasticSearch镜像为例 docker search ElasticSearch