多线程的初识

news2024/11/29 20:33:46

目录

  • 多线程
    • 线程的引入
    • 进程和线程的关系
    • 多线程可能存在的问题
    • 多线程程序的创建
      • Thread创建第一个多线程程序
      • 线程的抢占式执行
      • 查看java进程中的所有线程
      • 用Thread的其他方法创建多线程
        • 实现Runnable接口
        • 使用匿名内部类,继承Thread
        • 使用匿名内部类实现Runnable
        • 使用Lambda表达式(最简单,推荐写法)

多线程

线程的引入

我们前一节介绍了进程,引入进程这个概念的主要目的,是为了解决并发编程这样的问题
并发编程:CUP已经进入了多核心时代,CUP再往小了做很快就达到经典物理学就会失效,就会到了量子力学的范围里了,实现起来就很困难。想要进一步提高程序的执行速度,就要充分利用CPU的多核资源
其实,多进程编程已经解决并发编程的问题了。已经可以利用cpu多核资源了。
但是进程太重了(消耗资源多&速度慢)

  • 创建一个进程,开销比较大。
  • 销毁一个进程,开销也比大
  • 调度一个进程,开销还是比较大

说进程重,主要就重在资源分配/回收
为了解决这些问题,线程就应运而生,线程也叫做轻量级进程解决并发编程问题的前提下,让创建,销毁,调度的速度,更快一些。线程的就是把申请/释放资源的操作给省下来了。

举个例子就好比工厂加工的流水线。现在工厂的营收非常乐观,想要扩大生产线,那么有两种解决方案。

  • 方案1:再买一块低,建造一个工厂再配备这些流水线。
  • 方案2:在原有工厂的基础上再加一些机器和生产线,两套生产线共用一套资源。

上面的例子中方案2明显成本比方案1小很多,资源都可以复用之前的。这就是多线程的方案

进程和线程的关系

进程和对线程的关系,是进程包含线程。
一个进程可以包含一个线程也可以包含多个线程(不能没有)。
只启动第一个线程的时候开销比较大,后续线程就省事了。
同一个进程的多个线程之间,共用了进程的同一份资源(主要是指内存和文件描述表)
内存你线程1new的对象,在线程2,3,4里面都可以直接使用。
线程1打开的文件在线程2,3,4里面可以直接使用。
线程相比于进程来说,更轻量

上节进程的调度,相当于每个进程里面只有一个线程这种情况。如果每个进程里面有多个线程,每个线程都独立在CPU上执行 => 线程是操作系统调度的基本单位 每个线程也有自己的执行逻辑(执行流)

一个线程可以通过一个PCB描述。
一个进程里面可能对应着一个PCB也可能对应着多个。
之前介绍PCB里的状态,上下文,优先级,记账信息,都是各个线程自己的。各自记录各自的。但是同一个进程里的PCB之间,pid是一样的,内存指针和文件描述表也是一样的。
总结:

  • 进程包含一个及以上线程,线程是轻量级进程
  • 一个进程里 若干线程之间共享着 pid ,内存指针,文件描述表
  • 每个线程独立调度执行,享有自己的进程调度相关属性(状态,上下文,优先级,记账信息)
  • 进程是操作系统资源分配的基本单位
  • 线程是操作系统调度执行的基本单位

多线程可能存在的问题

我们就以工厂流水线为例
在这里插入图片描述

  1. 工厂为了加快生产速度多引入了几条流水线,这样确实可以增加速度,但是如果引入流水线太多,也不是一直能够提高速度的。工厂空间不够(类比CPU核心数量有限),太过拥挤,大家互相推推嚷嚷,会导致正在工作的流水线无法正常工作。
    线程太多,核心数目有限,不少开销反而浪费在线程调度上了
  2. 流水线加工过程中由于各个流水线都在同一个车间中,原料是共享的如果两个流水线同时加工一件商品的时候,加工原料归属会出现问题。
    在多进程中,就不会出现这种情况(多进程是把产品分好了,自己加工自己的)
  3. 假如两个流水线的生产的产品有特定编号,每个流水线负责一部分组件相互配合,如果加工错误,可能会导致整个工厂的流水线都不能进行工作了。
    如果一个线程抛出异常处理不好的话,很可能把整个进程都带走了,其他线程也挂了。

多线程程序的创建

关于线程的操作,操作系统提供了API。
java是个跨平台语言,很多操作系统提供的功能,都被JVM给封装好了。
在java研发学习过程中,不需要学习系统原生的API(C语言),只需要学习Java提供的API就行了。

Thread创建第一个多线程程序

Java操作多线程,最核心的类Thread

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

public class ThreadDemo1 {
    public static void main(String[] args) {
        MyThread t = new MyThread();
        t.start();//线程里的特殊方法,启动一个线程
    }
}

输出结果hello world
这个结果和直接输出hello world有什么不同呢?
直接调用t.run和直接调用t.start有什么区别呢?

这个MyThread类继承了Thread类重写了其中的run方法。
如果只打印hello world,这个Java进程主要只有一个线程(调用main方法的线程),主线程。通过t.start()主线程调用t.start,创建出一个新的线程,新的线程调用t.run。
main是一个线程, t.start()又启动了一个线程。开启了多线程程序的运行。
run只是描述了线程要做的工作。start是真正的在操作系统中搞了个线程,并让新线程调用run.
在这里插入图片描述
也就是调用操作系统的API,通过操作系统内核创建新线程的PCB,并且要把执行的指令交给这个PCB,当PCB被调度到CPU上执行的时候,也执行到了线程中的run方法中的代码。

线程的抢占式执行

class MyThread extends Thread{
    @Override
    public void run() {
        while (true){
            System.out.println("hello Thread");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

public class ThreadDemo1 {
    public static void main(String[] args) {
        MyThread t = new MyThread();
        t.start();
        while (true){
            System.out.println("hello main");
            try {
                Thread.sleep(1000);//线程休眠1000ms后面会具体介绍这个方法
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

在这里插入图片描述
操作系统调度线程的时候,抢占式执行,具体哪个线程先上,哪个线程后上,不确定,取决于操作系统调度器具体实现策略。虽然有优先级,但是在应用程序层面无法修改。从应用程序(代码)的角度,看到的效果,就好像是线程之间的调度顺序是随机的一样。内核里并非是随机的,但是干预因素太多,并且应用程序这一层也无法感知到细节,只能认为是随机的了。

查看java进程中的所有线程

可以使用jdk自带的工具jconsole 查看当前java进程中的所有线程

  1. 找到该目录下的jconsole
    在这里插入图片描述

  2. 进入自己运行的进程
    在这里插入图片描述
    进入后如果发现出现在这里插入图片描述
    如果你是初学,请不必担心,你电脑没有什么重要的数据。

  3. 找到线程在这里插入图片描述

调用栈,描述了当前方法之间的调用关系
在这里插入图片描述

用Thread的其他方法创建多线程

Java实现多线程的方法有很多种上面实现的第一个多线程程序用的就是继承Thread,重写run

实现Runnable接口

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

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

这种方式的好处是解耦合。目的是让线程和线程要干的活之间分离开。
未来如果需要改代码,不用多线程,使用多进程,或者线程池,或者协程池……此时的代码改动比较小

使用匿名内部类,继承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的子类(子类没有名字)所以才叫做匿名
  2. 创建了子类的实例,并让t引用指向了该实例.

使用匿名内部类实现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();
    }
}

写法和上面实现Runnable接口本质相同。只不过把实现Runnable的任务交给了匿名内部类语法。此处创建了一个类,实现Runnable,同时创建了类的实例,并且传给Thread的构造方法

使用Lambda表达式(最简单,推荐写法)

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

把任务用lambda表达式来描述,直接把lambda传给Thread的构造方法。

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

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

相关文章

嵌入式Linux驱动开发笔记(未完待续。。。)

一、Git仓库用法 1、linu终端输入下面命令安装 git clone https://e.coding.net/weidongshan/linux_course/linux_basic_develop.git2、 进入到GIT仓库目录 cd /D/abc/doc_and_source_for_mcu_mpu在doc_and_source_for_mcu_mpu目录下,执行以下命令获得资料的最新…

【1752. 检查数组是否经排序和轮转得到】

来源:力扣(LeetCode) 描述: 给你一个数组 nums 。nums 的源数组中,所有元素与 nums 相同,但按非递减顺序排列。 如果 nums 能够由源数组轮转若干位置(包括 0 个位置)得到&#xf…

appnium环境搭建

一、安装JDK 官网下载对应版本直接安装 二、安装Android SDK 官网下载对应版本直接安装 https://www.androiddevtools.cn/ 三、安装安卓模拟器 我使用的是夜神模拟器,官网下载直接安装 夜神安卓模拟器-安卓模拟器电脑版下载_安卓手游模拟器_手机模拟器_官网 …

springboot整合SpringSecurity并实现简单权限控制

目录 一、SpringSecurity介绍 案例效果: 二、环境准备 2.1 数据库 2.2 项目准备 三、确保项目没问题后开始使用 3.1、Security的过滤链: 3.2、自定义用户名密码登录: 方式1:将用户名密码写在配置文件里 方式2:使…

刷题之莲子的软件工程学和机械动力学以及物理热力学

目录 1、莲子的软件工程学 1)题目 2)题目解析 3)代码 2、莲子的机械动力学 2)题目解析 3)代码 3、莲子的物理热力学 1)、题目 2)题目解析 1、莲子的软件工程学 1)题目 题目背景…

Linux下的进程控制-进程程序替换

这篇主要说一下Linux下的进程控制中最后一部分内容:进程程序替换。 文章目录1. 进程程序替换1.1 为什么要进程程序替换1.2 替换原理1.3 如何进行程序替换1.3.1 execl函数1.3.2 引入子进程的程序替换1.3.3 execv函数1.3.4 execlp函数和execvp函数1.3.5 如何执行其它…

Flutter自定义对话框返回相关问题汇总

Flutter自定义对话框&#xff0c;禁用系统返回按钮 - WillPopScope 使用WillPopScope即可&#xff0c;重点onWillPop方法: Future<bool> _onWillPop()>new Future.value(false); 由于要弹出dialog&#xff0c;我这里是禁掉返回按钮&#xff0c;当然也可以在这里做一下…

基于SpringBoot的二手商品交易平台

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SpringBoot 前端&#xff1a;Vue、HTML 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#…

3.6.4、随机接入-CSMA/CA协议

无线局域网使用的协议 1、基本概念 对于上述无线局域网为什么 A 和 C 都检测不到对方的无线信号 因为 C 不在 A 的范围内&#xff0c;C 发送无线信号会导致 A 检测不到 C 在发送 对于上述使用广播信道的有线局域网就不会存在这样的问题 总线上某个主机发送的信号&#xff0…

JVM虚拟机字节码执行引擎——类文件和类加载之前必看

文章目录虚拟机字节码执行引擎运行时栈帧结构局部变量表&#xff08;Local Variables&#xff09;操作数栈动态链接&#xff08;Dynamic Linking&#xff09;方法返回地址附加信息方法调用解析分派虚方法和非虚方法普通调用指令&#xff1a;动态调用指令&#xff1a;动态类型语…

SpringBoot SpringBoot 原理篇 2 自定义starter 2.7 开启yml 提示功能

SpringBoot 【黑马程序员SpringBoot2全套视频教程&#xff0c;springboot零基础到项目实战&#xff08;spring boot2完整版&#xff09;】 SpringBoot 原理篇 文章目录SpringBootSpringBoot 原理篇2 自定义starter2.7 开启yml 提示功能2.7.1 问题引入2.7.2 开启yml提示功能2.…

【矩阵论】正规方程——求解

5.2 正规方程 AHAxAHb为Axb的正规方程\begin{aligned} A^HAxA^Hb为Axb的正规方程 \end{aligned} AHAxAHb为Axb的正规方程​ 5.2.1 正规方程必有解 正规方程 AHAxAHbA^HAxA^HbAHAxAHb 必有解 &#xff0c;且特解为 x0Abx_0A^bx0​Ab &#xff0c;使 AHAx0AHbA^HAx_0A^HbAHAx0​…

一文熟悉 Go 的循环结构 —— for 循环

哈喽大家好&#xff0c;我是陈明勇&#xff0c;今天分享的知识是 Go 的循环结构。如果本文对你有帮助&#xff0c;不妨点个赞&#xff0c;如果你是 Go 语言初学者&#xff0c;不妨点个关注&#xff0c;一起成长一起进步&#xff0c;如果本文有错误的地方&#xff0c;欢迎指出&a…

【Spring】——10、@PostConstruct注解和@PreDestroy注解

&#x1f4eb;作者简介&#xff1a;zhz小白 公众号&#xff1a;小白的Java进阶之路 专业技能&#xff1a; 1、Java基础&#xff0c;并精通多线程的开发&#xff0c;熟悉JVM原理 2、熟悉Java基础&#xff0c;并精通多线程的开发&#xff0c;熟悉JVM原理&#xff0c;具备⼀定的线…

OpenStack集群部署——Keystone部署(二)

三、Keyston-认证服务 3.1 Keyston介绍 Keyston介绍 补充 3.2 安装时间同步器 ----------------------------------------------------使用chrony安装------------------------------------------------- ####所有节点 #下载安装chrony yum -y install chrony #修改配置…

【心电信号】Simulink胎儿心电信号提取【含Matlab源码 1550期】

⛄一、心电信号简介 0 引言 心电信号是人类最早研究的生物信号之一, 相比其他生物信号更易于检测, 且具有直观的规律。心电图的准确分析对心脏病的及早治疗有重大的意义。人体是一个复杂精密的系统, 有许多不可抗的外界因素, 得到纯净的心电信号非常困难。可以采用神经网络算法…

MongoDB 分片集群

之前说到了主从集群&#xff0c;关于主从集群的搭建以及细节后面会再次分享&#xff0c;这次我们先初步来看看 分片集群 举个例子 例如我们有几百G甚至更多的数据&#xff0c;可是我们只有单个副本集&#xff0c;数据量这么大&#xff0c;网络 IO &#xff0c;CPU &#xff0c…

《深度学习的数学》chap1 神经网络的思想

《深度学习的数学》chap1 神经网络的思想 文章目录1-1 神经网络和深度学习神经网络用神经网络实现的人工智能“人教导机器”类型的人工智能的问题1-2 神经元工作的数学表示整理神经元的工作神经元工作的数学表示点火条件的图形表示1-3 激活函数&#xff1a;将神经元的工作一般化…

开源项目-排班管理系统,考勤管理系统

哈喽&#xff0c;大家好&#xff0c;今天给大家带来一个开源系统-排版管理系统 ​​​​​​​git上搜索可以FinalScheduler-master可以了解详情 也可以通过csdn下载​​​​​​​ 该系统主要用于人员的排班使用&#xff0c;主要用人员管理&#xff0c;排班管理&#xff0c…

Java-ForkJoinPool(线程池-工作窃取算法)

文章目录概述工作窃取算法工作窃取算法的优缺点使用 ForkJoinPool 进行分叉和合并ForkJoinPool使用RecursiveActionRecursiveTaskFork/Join 案例Demo概述 Fork 就是把一个大任务切分为若干个子任务并行地执行&#xff0c;Join 就是合并这些子任务的执行结果&#xff0c;最后得到…