【Java 并发编程】初识多线程
前言
到目前为止,我们学到的都是有关 “顺序” 编程的知识,即程序中所有事物在任意时刻都只能执行一个步骤。例如:在我们的 main 方法中,都是多个操作以 “从上至下” 的顺序调用方法以至结束的。
虽然 “顺序” 编程能够解决相当大的一部分问题,但是当所有的方法全部推在一个 “队列” 里面,如果前面的方法没有执行完,后面的方法就轮不到。这样程序的运行效率必然十分低下。对于一些问题,能够同时执行程序的多个部分,就会十分方便且必要。而并发编程的设计就是为了专门解决这一类问题。
前期回顾:Java 之深入理解 String、StringBuilder、StringBuffer
文章代码:码云地址
目录
前言
并发与并行的简单区别
并发
总结
并行
总结
进程与线程的简单区别
进程
线程
进程与线程的区别总结
多线程的理解
创建线程任务的方式
继承 Thread 类
实现 Runnable 接⼝
匿名内部类创建
lambda 表达式创建
并发与并行的简单区别
并发
关于并发这里举个例子:一家餐厅来了三个食客,但是厨师只有一位,怎么出餐就成了问题。<1> 按食客进店的先后顺序上(之前提到的顺序编程),那么后两位食客等的时间久了自然不乐意肯定会掀桌子。<2> 三个人的菜同时做,虽然只有一个厨师(一个CPU核心),但是有三口锅(三个线程),当一个菜烧制的差不多的时候立刻就切换到另一个菜。这样几乎可以做到三个菜的同时出餐。
在计算机中,由于 CPU 的运算速度非常的快,切换的速度也非常的快,如上图,可能执行到QQ的指令2就立刻跳转到微信的线程,站在宏观角度,可以看成是同时执行(并发执行)的。
总结
并发是指两个或多个任务在同一时间间隔内发生。比如在单核CPU上运行 16 个线程,由于核数限制,这 16 个线程无法在同一时刻运行,所以CPU只能采用时间片切换的方式来运行。
并行
关于并行这里举个例子:一家餐厅来了三个食客,但是厨师刚好有三位。这样怎么出餐就不成问题了。我们只需每桌安排一个厨师,就可完成出餐任务。
在计算机中,这就意味着有一个CPU有多个核心,同一时刻的程序可以分配到多个CPU处理器上,实现多任务,并行执行。
总结
当有多个CPU核心时,在同一个时刻可以同时运行多个任务,这种方式叫并行。
比如,3核CPU可以同时运行3个线程。
顺序编程是指多个操作按次序执行,并行编程是指多个操作同时执行,而并发编程将同一个操作分解为多个部分并允许无序执行。
进程与线程的简单区别
进程
进程是指正在运行中的程序实例。每个进程都是一个独立的执行单元,拥有自己的内存空间和系统资源。一个进程可以包含多个线程,是程序执行的基本单位一个在内存中运行的应用程序。比如在 Windows 系统中,一个运行的 xx.exe 就是一个进程。我们打开任务管理器就可以看到此时电脑的进程。
线程
线程是进程内可计划执行的实体。 进程的所有线程共享其虚拟地址空间和系统资源。一个进程至少有一个线程,即主线程 main;一个进程可以运行多个线程,多个线程可共享数据。
与进程不同的是同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、虚拟机栈和本地方法栈,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
进程与线程的区别总结
线程具有许多传统进程所具有的特征,故又称为轻型进程或进程元;而把传统的进程称为重型进程,它相当于只有一个线程的任务。在引入了线程的操作系统中,通常一个进程都有若干个线程,至少包含一个线程。
根本区别:进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位。 资源开销:每个进程都有独立的代码和数据空间,程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器,线程之间切换的开销小。
包含关系:如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。 内存分配:同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的。 影响关系:一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。 执行过程:每个独立的进程有程序运行的入口、顺序执行序列和程序出口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,两者均可并发执行。
多线程的理解
这里先举个例子 ~
单进程
猴子吃桃, 一只猴子在一间包间内需要吃一百个桃,我们知道这其实效率是很低的。那么我们可以将猴子吃桃当成是一个进程处理一个大型项目,那么这样搞会导致程序运行卡顿。所以我们需要将其优化。
多进程
我们可以将吃完 100 颗桃子的任务分给4子猴子完成,虽然效率提升了,但是每只猴子都需要开一间包间吃桃子,这就导致了资源的开销大。将一个程序交给4个进程也是一样的道理。
多线程
那么我在一个包间让多个猴子完成吃完桃子的计划,这样的安排是最合理的。既保证了效率,也节省了空间。那么猴子是不是越多效率越快呢?其实并不然,因为座位有限,当座位无法容纳更多的猴子,此时再添加猴子只会让它们相互 “争夺” 吃桃子的位置,从而影响吃桃的效率。
多线程也是如此:一个进程中可以拥有很多线程来辅助程序的运行,这里提升效率的因素是 “并行”。但是如果线程太多超过了CPU的核心数目,此时就无法完成所有线程的并行执行,就会势必会造成严重的 “线程冲突”。
创建线程任务的方式
继承 Thread 类
线程可以驱动任务,因此你需要一种描述任务的方式。这可以继承 Thread 这个线程类。要想定义任务,我们只需要重写 Thread 类中的 run() 方法即可,使得该任务执行你的命令:
class MyThread extends Thread{@Overridepublic void run() {while(true){// 即将创建出的线程,要执行的逻辑System.out.println("Hello");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}public class Demo1{public static void main(String[] args) {MyThread thread = new MyThread();// 创建线程thread.start();while(true){System.out.println("World");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
运行截图:
<1> 任务中的 run() 方法通常总会以某种方式的循环,使得任务一直执行下去直到不需要,所以需要设置循环条件。通常 run() 会被写成无限循环形式,这就意味着,除非有某个条件使得 run() 停止,否则它将会永远的运行下去。
<2> 由于该程序是死循环,为了更好的观察加上了sleep。而我们要捕获 sleep 方法有可能会抛出 InterruptedException 异常。一般来说,中断请求终止一个线程。相应的,如果抛出 InterruptedException 时,run() 方法就会退出。
<3>
World
Hello
Hello
World
Hello
World
Hello
World
...
通过打印结果我们可以发现,这个线程的输出是交错的,说明它们在并发的进行。
<4> 以上的程序一共有两个线程,一个是 main 线程,一个是我们用 Thread 创建的线程。但是如果我们直接调用 Thread 的 run() 的方法,只会在 main 线程中执行这个方法,而没有开辟新的线程。
<5> 虽然以上方法可以创建线程但是不推荐使用,我们应该将并行运行的任务与运行机制解耦合。如果有多个任务,为每个任务分别创建一个线程开销太大。
实现 Runnable 接⼝
class MyRunnable implements Runnable {@Overridepublic void run() {// 线程需要完成的逻辑while(true){System.out.println("Hello");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}};
}public class Demo2 {public static void main(String[] args) {// 创建 Thread 类实例, 调⽤ Thread 的构造⽅法时将 Runnable 对象作为参数MyRunnable myRunnable = new MyRunnable();Thread t1 = new Thread(myRunnable);t1.start();while (true) {System.out.println("World");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
匿名内部类创建
public static void main(String[] args) {Thread t = new Thread(new Runnable() {@Overridepublic void run(){while(true){System.out.println("Hello World");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}});t.start();}
lambda 表达式创建
public static void main(String[] args) {Thread t = new Thread(()->{while(true){System.out.println("Hello World");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});t.start();}
相关文章:

【Java 并发编程】初识多线程
前言 到目前为止,我们学到的都是有关 “顺序” 编程的知识,即程序中所有事物在任意时刻都只能执行一个步骤。例如:在我们的 main 方法中,都是多个操作以 “从上至下” 的顺序调用方法以至结束的。 虽然 “顺序” 编程能够解决相当…...

Linux下载安装MySQL8.4
这里写目录标题 一、准备工作查看系统环境查看系统架构卸载已安装的版本 二、下载MySQL安装包官网地址 三、安装过程上传到服务器目录解压缩,设置目录及权限配置my.cnf文件初始化数据库配置MySQL开放端口 一、准备工作 查看系统环境 确认Linux系统的版本和架构&am…...

强化学习笔记之【DDPG算法】
强化学习笔记之【DDPG算法】 文章目录 强化学习笔记之【DDPG算法】前言:原论文伪代码DDPG算法DDPG 中的四个网络代码核心更新公式 前言: 本文为强化学习笔记第二篇,第一篇讲的是Q-learning和DQN 就是因为DDPG引入了Actor-Critic模型&#x…...

c++继承(下)
c继承(下) (1)继承与友元(2)继承与静态成员(3)多继承及其菱形继承问题3.1 继承模型3.2 虚继承3.3 多继承中指针偏移问题 (4)继承和组合(9…...

数据结构 ——— 单链表oj题:反转链表
目录 题目要求 手搓一个简易链表 代码实现 题目要求 给你单链表的头节点 head ,请你反转链表,并返回反转后的链表 手搓一个简易链表 代码演示: struct ListNode* n1 (struct ListNode*)malloc(sizeof(struct ListNode)); assert(n1);…...
前端项目npm install报错解决的解决办法
报错问题一: [rootspug-api spug_web]# npm install npm WARN deprecated xterm4.19.0: This package is now deprecated. Move to xterm/xterm instead. npm WARN deprecated workbox-google-analytics4.3.1: It is not compatible with newer versions of GA starting with v…...
vue双向绑定/小程序双向绑定区别
Vue双向绑定与小程序双向绑定在实现方式、语法差异以及功能特性上均存在显著区别。以下是对这两者的详细比较: 一、实现方式 Vue双向绑定 Vue的双向绑定主要通过其响应式数据系统实现。Vue使用Object.defineProperty()方法(或在Vue 3中使用Proxy对象&am…...
华为OD机试真题---字符串变换最小字符串
题目描述: 给定一个字符串s,最多只能进行一次变换,返回变换后能得到的最小字符串(按照字典序进行比较)。 变换规则: 交换字符串中任意两个不同位置的字符。 输入描述: 一串小写字母组成的字符串s 输出描述: 按照要求进行变换得到的最小字符串 补…...

JAVA基础面试题汇总(持续更新)
1、精确运算场景使用浮点型运算问题 精确运算场景(如金融领域计算应计利息)计算数字,使用浮点型,由于精度丢失问题,会导致计算后的结果和预期不一致,使用Bigdecimal类型解决此问题,示例代码如下…...
设计模式-创建型-常用:单例模式、工厂模式、建造者模式
单例模式 概念 一个类只允许创建一个对象(或实例),那这个类就是单例类,这种设计模式就叫做单例模式。对于一些类,创建和销毁比较复杂,如果每次使用都创建一个对象会很耗费性能,因此可以把它设…...

【数据结构】【链表代码】随机链表的复制
/*** Definition for a Node.* struct Node {* int val;* struct Node *next;* struct Node *random;* };*/typedef struct Node Node; struct Node* copyRandomList(struct Node* head) {if(headNULL)return NULL;//1.拷贝结点,连接到原结点的后面Node…...

Linux 系统五种帮助命令的使用
Linux 系统五种帮助命令的使用 本文将介绍 Linux 系统中常用的帮助命令,包括 man、–help、whatis、apropos 和 info 命令。这些命令对于新手和有经验的用户来说,都是查找命令信息、理解命令功能的有力工具。 文章目录 Linux 系统五种帮助命令的使用一…...

Vueron引领未来出行:2026年ADAS激光雷达解决方案上市路线图深度剖析
Vueron ADAS激光雷达解决方案路线图分析:2026年上市展望 Vueron近期发布的ADAS激光雷达解决方案路线图,标志着该公司在自动驾驶技术领域迈出了重要一步。该路线图以2026年上市为目标,彰显了Vueron对未来市场趋势的精准把握和对技术创新的坚定…...

Java | Leetcode java题解之第458题可怜的小猪
题目: 题解: class Solution {public int poorPigs(int buckets, int minutesToDie, int minutesToTest) {if (buckets 1) {return 0;}int[][] combinations new int[buckets 1][buckets 1];combinations[0][0] 1;int iterations minutesToTest /…...
怎么不改变视频大小的情况下,修改视频的时长
视频文件太大怎么变小?不影响画质的四种方法 怎么不改变视频大小的情况下,修改视频的时长 截取结尾的时间你可以使用 ffmpeg 来裁剪视频的结尾部分。假设你想去掉视频最后的3秒钟,可以先使用 ffmpeg 获取视频的总时长,然后通过指定一个新的…...

数据结构:AVL树
前言 学习了普通二叉树,发现普通二叉树作用不大,于是我们学习了搜索二叉树,给二叉树新增了搜索、排序、去重等特性, 但是,在极端情况下搜索二叉树会退化成单边树,搜索的时间复杂度达到了O(N),这…...

系统守护者:使用PyCharm与Python实现关键硬件状态的实时监控
目录 前言 系统准备 软件下载与安装 安装相关库 程序准备 主体程序 更改后的程序: 编写.NET程序 前言 在现代生活中,电脑作为核心工具,其性能和稳定性的维护至关重要。为确保电脑高效运行,我们不仅需关注软件优化…...

【工作流引擎集成】springboot+Vue+activiti+mysql带工作流集成系统,直接用于业务开发,流程设计,工作流审批,会签
前言 activiti工作流引擎项目,企业erp、oa、hr、crm等企事业办公系统轻松落地,一套完整并且实际运用在多套项目中的案例,满足日常业务流程审批需求。 一、项目形式 springbootvueactiviti集成了activiti在线编辑器,流行的前后端…...

SumatraPDF一打开就无响应怎么办?
结论:当前安装版不论32位还是64位都会出现问题。使用portable免安装版未发现相关问题。——sumatrapdf可以用于pdf, epub, mobi 等格式文件的浏览。 点击看相关问题和讨论...

棋牌灯控计时计费系统软件免费试用版怎么下载 佳易王计时收银管理系统操作教程
一、前言 【试用版软件下载,可以点击本文章最下方官网卡片】 棋牌灯控计时计费系统软件免费试用版怎么下载 佳易王计时收银管理系统操作教程 棋牌计时计费软件的应用也提升了顾客的服务体验,顾客可以清晰的看到自己的消费时间和费用。增加了消费的透明…...
脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)
一、数据处理与分析实战 (一)实时滤波与参数调整 基础滤波操作 60Hz 工频滤波:勾选界面右侧 “60Hz” 复选框,可有效抑制电网干扰(适用于北美地区,欧洲用户可调整为 50Hz)。 平滑处理&…...
React hook之useRef
React useRef 详解 useRef 是 React 提供的一个 Hook,用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途,下面我将全面详细地介绍它的特性和用法。 基本概念 1. 创建 ref const refContainer useRef(initialValue);initialValu…...
Spring Boot 实现流式响应(兼容 2.7.x)
在实际开发中,我们可能会遇到一些流式数据处理的场景,比如接收来自上游接口的 Server-Sent Events(SSE) 或 流式 JSON 内容,并将其原样中转给前端页面或客户端。这种情况下,传统的 RestTemplate 缓存机制会…...

2.Vue编写一个app
1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...
python如何将word的doc另存为docx
将 DOCX 文件另存为 DOCX 格式(Python 实现) 在 Python 中,你可以使用 python-docx 库来操作 Word 文档。不过需要注意的是,.doc 是旧的 Word 格式,而 .docx 是新的基于 XML 的格式。python-docx 只能处理 .docx 格式…...

DBAPI如何优雅的获取单条数据
API如何优雅的获取单条数据 案例一 对于查询类API,查询的是单条数据,比如根据主键ID查询用户信息,sql如下: select id, name, age from user where id #{id}API默认返回的数据格式是多条的,如下: {&qu…...

(转)什么是DockerCompose?它有什么作用?
一、什么是DockerCompose? DockerCompose可以基于Compose文件帮我们快速的部署分布式应用,而无需手动一个个创建和运行容器。 Compose文件是一个文本文件,通过指令定义集群中的每个容器如何运行。 DockerCompose就是把DockerFile转换成指令去运行。 …...
音视频——I2S 协议详解
I2S 协议详解 I2S (Inter-IC Sound) 协议是一种串行总线协议,专门用于在数字音频设备之间传输数字音频数据。它由飞利浦(Philips)公司开发,以其简单、高效和广泛的兼容性而闻名。 1. 信号线 I2S 协议通常使用三根或四根信号线&a…...
Git常用命令完全指南:从入门到精通
Git常用命令完全指南:从入门到精通 一、基础配置命令 1. 用户信息配置 # 设置全局用户名 git config --global user.name "你的名字"# 设置全局邮箱 git config --global user.email "你的邮箱example.com"# 查看所有配置 git config --list…...

脑机新手指南(七):OpenBCI_GUI:从环境搭建到数据可视化(上)
一、OpenBCI_GUI 项目概述 (一)项目背景与目标 OpenBCI 是一个开源的脑电信号采集硬件平台,其配套的 OpenBCI_GUI 则是专为该硬件设计的图形化界面工具。对于研究人员、开发者和学生而言,首次接触 OpenBCI 设备时,往…...