【Java EE初阶 --- 多线程(初阶)】线程安全问题
乐观学习,乐观生活,才能不断前进啊!!!
我的主页:optimistic_chen
我的专栏:c语言 ,Java
欢迎大家访问~
创作不易,大佬们点赞鼓励下吧~
文章目录
- 线程不安全的原因
- 根本原因
- 非原子性操作
- 多个线程修改同一个变量
- 内存可见性
- 解决线程安全问题
- 内存可见性问题
- 加锁(打包原子性)
- 加锁\解锁
- 锁对象
- 使用synchronized修饰方法
- 监视器锁monitor lock
- 可重入性
- 死锁
- 避免死锁
- Java标准可中的线程安全类
- 线程协调
- wait(等待)
- notify(通知)
- notifyAll
- 完结
线程不安全的原因
根本原因
多线程优点很明显,大大提高了线程的运行效率,但是它也有一个巨大的隐患:线程是并发执行的,而且调度是随机的(根本原因)。也就是说,随机调度使⼀个程序在多线程环境下, 执⾏顺序存在很多的变数 程序猿必须保证 在任意执⾏顺序下 , 代码都能正常⼯作。
看下面的代码,利用多线程求和,我们的想法是两个线程各自增5000,最后结果就是10000,但是我们多次运行发现,每一次执行的结果都不同。
在我们看来count++是一个递增操作,但是在CPU眼里,它有三个指令:
1. load -> 把内存中count数据读到 CPU寄存器
2. add -> 在指定寄存器中进行+1操作
3. save -> 把寄存器中数据写回到内存
CPU在执行这三条指令时,随时会触发线程的随机调度。。。
非原子性操作
我依稀记得,原子性这个特性是数据库中的东西,具有不可再分的特性。
如果修改操作只是对应到一个CPU指令,不会出现“一条指令执行一半”的情况,就可以认为是原子的;相反如果对应多个CPU指令,就不是原子的。
随机到正确的调度
随机到错误的调度
经过讨论:
如果两个线程load到的数据都是0,意味着一定会少加一次。
必须严格一个load到0,一个load到1,结果才正确;也就是一个线程的load必须在另一个线程的save后面
多个线程修改同一个变量
如果一个线程修改一个变量---- 可以
如果多个线程,不是同时修改同一个变量— 可以
如果多个线程修改不同变量— 可以
如果多个线程读取同一个变量— 可以
内存可见性
如果某一个线程针对共享变量值修改,能够及时被其他线程看到
此时虽然输入了非0的值,但是t1线程还没有执行完。
由运行结果可知,t2线程已经修改,但是t1线程没有读到已经修改后的值,这里就出现了bug.
解决线程安全问题
内存可见性问题
为什么会出现内存不能及时读取这种问题?我们来看看Java 内存模型 (JMM): Java虚拟机规范中定义了Java内存模型的解释(翻译后):
每个线程,有一个自己的”工作内存(work memory)“,同时这些线程共享同一个“主内存(main memory)”,当一个线程循环进行上述变量操作的时候,就会把主内存中的数据,拷贝到该线程的工作内存中后续另一个线程修改,也是先修改自己的工作内存,拷贝到主内存里。由于第一个线程仍然在读自己的工作内存,因此感知不到主内存的变换。 |
如果学过计算机组成原理,这里稍微解释一下就会很清楚:工作内存不是内存,而是CPU的寄存器;主内存才是我们平时所说的内存。文档中之所以没有明确使用“寄存器”,而是用work memory来描述,主要是为了“一次编译,到处运行”。⽬的是屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到⼀致的并发效果。
在语法中,引入 volatile 关键字,通过这个关键字来修饰变量,编译器对这个变量的读取操作,就不会轻易被优化到寄存器中…(因为会得“易变”)
volatile关键字只是解决了内存可见性问题,解决原子性问题还需要加锁来完成。
加锁(打包原子性)
根本原因是操作系统的底层设计,无法改变…
主要解决方案,通过加锁(封装)一个原子特性,让不是原子的操作,打包为一个原子操作。
针对之前count++的操作,先给t1中count++加锁,等待t1计算完毕,再计算t2中的count++(给t2同样加锁)。
这里加锁比较抽象,不是禁止线程被调度走,而是禁止其他线程重新加这个锁,避免其他线程的插队。
加锁\解锁
加锁 \ 解锁 本身是操作系统提供的api,大多数都是对这样的api进行封装。
但是Java是通过synchronized 这样的关键字,来实现类似效果
synchronized(){//加锁//代码块//count++
}//解锁
锁对象
在Java中,任何一个对象都能叫做“锁”
这个锁的类型不重要,重要的是,是否有多个线程尝试针对这同一个对象加锁。
(这种时候才会产生互斥),如果是不同的锁对象,不会有互斥效果,线程安全没有解决。(一个对象只有一个用途比较好)
使用synchronized修饰方法
锁任意对象
private Object locker = new Object();public void method() {synchronized (locker) {}}
锁当前对象
private Object locker = new Object();public void method() {synchronized (this) {}}
直接修饰普通方法:锁的Demo对象
public class Demo {public synchronized void methond() {}
}
修饰静态方法:锁的Demo类的对象
public class Demo {public synchronized static void methond() {}
}
总结:
多个线程针对同一个对象加锁,才会产生互斥(锁冲突)。
synchronized修饰普通方法,相当于给this加锁
synchronized修饰静态方法,相当于给类对象加锁
监视器锁monitor lock
比较少见
Lock locker=new locker();
locker.lock();
.....
locker.unlock();
//容易忘记unlock;
可重入性
观察上面两个代码块,我们发现对add()方法加了两次锁。
我们分析一下,针对第一次加锁,可以成功(锁没有人使用);第二次加锁,锁对象已经被占用,那么第二次加锁就会阻塞等待。
这个时候为了要解除阻塞等待(继续执行),只能使第一次加锁被释放;但是第一次加锁要释放必须执行到红色方框,为了执行到红色方框又必须执行第二次解锁,此时就陷入了死循环(死锁)。
反转来了:当你运行此程序时,发现它结果完全正确。
因为Java中的 synchronized 是可重⼊锁(内在), 因此没有上⾯的问题.
值得注意的是,这里可重入特性有一个前提:所有锁都掌握在同一个线程手中,也就是锁对象内部知道当前线程是哪一个,如果有非当前线程来加锁,还是会造成阻塞等待。
<举个例子>小帅去追小美交朋友,小美同意了,那么小美就相当于被小帅加锁,之后小帅一直对小美“交朋友”(懂得都懂),小帅不会被拒之门外;但是如果有小丑来找小美交朋友,那他就得等待小帅和小美分手,他才有机会.... |
站在JVM的视角,如何才知道哪个 “{” 是真正的加锁、哪个 “}” 是真正的解锁呢?
在可重⼊锁的内部, 包含了 “线程持有者” 和 “计数器” 两个信息.
• 如果某个线程加锁的时候, 发现锁已经被⼈占⽤, 但是恰好占⽤的正是⾃⼰(“{”), 那么仍然可以继续获取到锁, 并让计数器⾃增.
• 解锁(”}“)的时候计数器递减为 0 的时候, 才真正释放锁. (才能被别的线程获取到)
死锁
第一种死锁:一个线程,一把锁,连续加锁两次(正常会造成死锁,synchronized有可重入特性避免出现死锁)
第二种死锁:两个线程,两把锁,每个线程获得一把锁后,都想要对方的锁。
众所周知,核潜艇中的”小男孩“要发射需要舰长和副舰长两人的钥匙,战争中,如果收到命令发射核弹,需要正副舰长拿出钥匙同时启动。 |
如果舰长收到命令,在有自己钥匙的前提下,还需要副舰长的钥匙。 |
Thread t1=new Thread(()->{//舰长拿出钥匙synchronized (locker1){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}//尝试拿到副舰长的钥匙synchronized (locker2){System.out.println("t1线程拿到两个锁");}}});
如果副舰长收到命令,在有自己钥匙的前提下,还需要舰长的钥匙。 |
Thread t2=new Thread(()->{//副舰长拿出钥匙synchronized (locker2){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}//尝试拿到舰长的钥匙synchronized (locker1){System.out.println("t2线程拿到两个锁");}}});
运行程序发现,两个线程都以BLOCKED(竞争锁)阻塞了
总结一下死锁出现的原因:
- 锁是互斥的,一个线程拿到锁后,另一个线程再尝试获得锁,必须要阻塞等待(锁的基本特性)
- 锁是不可剥夺的,一个线程拿到锁后,另一个线程再尝试获得锁,不能直接去抢,必须要阻塞等待(锁的基本特性)
- 请求和保持,一个线程拿到锁后,不释放锁1的情况下,去获得锁2 (嵌套)
- 循环等待,多个线程,多个锁之间构成”循环“
避免死锁
结合死锁出现的原因,1和2都是锁的基本特性,无法改变,那么只有对3和4下功夫了。
3.请求和保持:一个线程拿到锁后,不释放锁1的情况下,再由一个线程去获得锁2(并列)
4.循环等待:对加锁的顺序做出约定
Java标准可中的线程安全类
虽然有加锁来保证线程安全,但是万事没有绝对,而且加锁是要付出相应的代价(代码可能因为锁竞争,造成阻塞等待,影响程序执行效率)
Java中String虽然没有加锁,但是它内部具有不可修改的特性,使其天然的保证了线程安全。
线程协调
我们知道**,线程之间是抢占式执行的**,如果我们程序员不加以干涉,我们很难得知线程之间执行的先后顺序。程序员对线程的设计都是具有逻辑顺序的,这时候就需要我们合理的协调多个线程之间的执行先后顺序。
回想之前有关等待的部分:
join是等另一个线程彻底结束,才继续执行,不符合我们的需求。
锁对于等待随机性太复杂,也不符合需求。
wait(等待)
wait方法的作用:
1. 使当前线程代码的线程进行等待
2. 释放当前锁(这就意味着wait方法必须和synchronized配合使用,因为要解锁的前提是当前对象为加锁状态)
3. 满足一定条件被唤醒,重新尝试获取这个锁
Object object=new Object();synchronized (object){object.wait();}
详细的操作是:
这里等待其他线程逻辑执行完毕,就可以使用notify方法唤醒wait,当前线程继续执行。
notify(通知)
notify的作用;
1. 同步使用在等待代码块中,通知 等待该对象的对象锁的其他线程,使它们重新获取该对象的对象锁。
2. 如果有多个等待线程,那么随机挑选出有wait状态的线程
3. 注意:在notify方法结束后,当前线程线程不会立刻释放该对象锁,要等执行notify的线程执行完毕后才会释放对象锁
Object locker=new Object();Thread t1=new Thread(()->{synchronized (locker){try {locker.wait();} catch (InterruptedException e) {throw new RuntimeException();}}});
Thread t2=new Thread(()->{Scanner scan=new Scanner(System.in);scan.next();//等待输入:约等于阻塞synchronized (locker){locker.notify();}});
如果不是同一个对象,那么两个线程无法沟通,就不能相互作用
notifyAll
Thread t3=new Thread(()->{Scanner scan=new Scanner(System.in);scan.next();//等待输入:约等于阻塞synchronized (locker){locker.notifyAll();//一次性唤醒所有wait线程}});
注意: 虽然是同时唤醒 2 个线程, 但是这 2 个线程需要同时加锁.只有其中一个才能成功加锁,所以并不是同时执⾏, ⽽仍然是有先有后的执⾏.
完结
可以点一个免费的赞并收藏起来~
可以点点关注,避免找不到我~ ,我的主页:optimistic_chen
我们下期不见不散 ~ ~ ~
相关文章:
【Java EE初阶 --- 多线程(初阶)】线程安全问题
乐观学习,乐观生活,才能不断前进啊!!! 我的主页:optimistic_chen 我的专栏:c语言 ,Java 欢迎大家访问~ 创作不易,大佬们点赞鼓励下吧~ 文章目录 线程不安全的原因根本原因…...
从InfluxDB到StarRocks:Grab实现Spark监控平台10倍性能提升
Grab 是东南亚领先的超级应用,业务涵盖外卖配送、出行服务和数字金融,覆盖东南亚八个国家的 800 多个城市,每天为数百万用户提供一站式服务,包括点餐、购物、寄送包裹、打车、在线支付等。 为了优化 Spark 监控性能,Gr…...
《Redis应用实例》学习笔记,第一章:缓存文本数据
前言 最近在学习《Redis应用实例》,这本书并没有讲任何底层,而是聚焦实战用法,梳理了 32 种 Redis 的常见用法。我的笔记在 Github 上,用 Jupyter 记录,会有更好的阅读体验,作者的源码在这里:h…...
Redis 缓存
缓存介绍 Redis 最主要三个用途: 1)存储数据(内存数据库) 2)消息队列 3)缓存 对于硬件的访问速度,通常有以下情况: CPU 寄存器 > 内存 > 硬盘 > 网络 缓存的核心…...
Apache Flink 与 Flink CDC:概念、联系、区别及版本演进解析
Apache Flink 与 Flink CDC:概念、联系、区别及版本演进解析 在实时数据处理和流式计算领域,Apache Flink 已成为行业标杆。而 Flink CDC(Change Data Capture) 作为其生态中的重要组件,为数据库的实时变更捕获提供了强大的能力。 本文将从以下几个方面进行深入讲解: 什…...
缓存(4):常见缓存 概念、问题、现象 及 预防问题
常见缓存概念 缓存特征: 命中率、最大元素、清空策略 命中率:命中率返回正确结果数/请求缓存次数 它是衡量缓存有效性的重要指标。命中率越高,表明缓存的使用率越高。 最大元素(最大空间):缓存中可以存放的最大元素的…...
实战项目6(09)
目录 任务场景一 【r1配置】 【r2配置】 【r3配置】 任务场景二 【r1配置】 【r2配置】 【r3配置】 任务场景三 【r1配置】 【r2配置】 【r3配置】 任务场景一 按照下图完成网络拓扑搭建和配置 任务要求:在…...
MySQL 数据库故障排查指南
MySQL 数据库故障排查指南 本指南旨在帮助您识别和解决常见的 MySQL 数据库故障。我们将从问题识别开始,逐步深入到具体的故障类型和排查步骤。 1. 问题识别与信息收集 在开始排查之前,首先需要清晰地了解问题的现象和范围。 故障现象: 数…...
MacOS Python3安装
python一般在Mac上会自带,但是大多都是python2。 python2和python3并不存在上下版本兼容的情况,所以python2和python3可以同时安装在一台设备上,并且python3的一些语法和python2并不互通。 所以在Mac电脑上即使有自带python,想要使…...
锁相放大技术:从噪声中提取微弱信号的利器
锁相放大技术:从噪声中提取微弱信号的利器 一、什么是锁相放大? 锁相放大(Lock-in Amplification)是一种用于检测微弱信号的技术,它能够从强噪声背景中提取出我们感兴趣的特定信号。想象一下在嘈杂的派对上听清某个人…...
机器学习总结
1.BN【batch normalization】 https://zhuanlan.zhihu.com/p/93643523 减少 2.L1L2正则化 l1:稀疏 l2:权重减小 3.泛化误差 训练误差计算了训练集的误差,而泛化误差是计算全集的误差。 4.dropout 训练过程中神经元p的概率失活 一文彻底搞懂深度学习&#x…...
基于神经网络的无源雷达测向系统仿真实现
基于神经网络的无源雷达测向系统仿真实现 项目概述 本项目实现了基于卷积神经网络(CNN)的无源雷达方向到达角(DOA)估计系统。通过深度学习方法,系统能够从接收到的雷达信号中准确估计出信号源的方向,适用于单目标和多目标场景。相比传统的DOA估计算法&…...
《用MATLAB玩转游戏开发》Flappy Bird:小鸟飞行大战MATLAB趣味实现
《用MATLAB玩转游戏开发:从零开始打造你的数字乐园》基础篇(2D图形交互)-Flappy Bird:小鸟飞行大战MATLAB趣味实现 文章目录 《用MATLAB玩转游戏开发:从零开始打造你的数字乐园》基础篇(2D图形交互…...
【C/C++】跟我一起学_C++同步机制效率对比与优化策略
文章目录 C同步机制效率对比与优化策略1 效率对比2 核心同步机制详解与适用场景3 性能优化建议4 场景对比表5 总结 C同步机制效率对比与优化策略 多线程编程中,同步机制的选择直接影响程序性能与资源利用率。 主流同步方式: 互斥锁原子操作读写锁条件变量无锁数据…...
linux 三剑客命令学习
grep Grep 是一个命令行工具,用于在文本文件中搜索打印匹配指定模式的行。它的名称来自于 “Global Regular Expression Print”(全局正则表达式打印),它最初是由 Unix 系统上的一种工具实现的。Grep 工具在 Linux 和其他类 Unix…...
【js基础笔记] - 包含es6 类的使用
文章目录 js基础js 预解析js变量提升 DOM相关知识节点选择器获取属性节点创建节点插入节点替换节点克隆节点获取节点属性获取元素尺寸获取元素偏移量标准的dom事件流阻止事件传播阻止默认行为事件委托 正则表达式js复杂类型元字符 - 基本元字符元字符 - 边界符元字符 - 限定符元…...
《Linux命令行大全(第2版)》PDF下载
内容简介 本书对Linux命令行进行详细的介绍,全书内容包括4个部分,第一部分由Shell的介绍开启命令行基础知识的学习之旅;第二部分讲述配置文件的编辑,如何通过命令行控制计算机;第三部分探讨常见的任务与必备工具&…...
补补表面粗糙度的相关知识(一)
表面粗糙度,或简称粗糙度,是指表面不光滑的特性。这个在机械加工行业内可以说是绝绝的必备知识之一,但往往也是最容易被忽略的,因为往往天天接触的反而不怎么关心,或者没有真正的去认真学习掌握。对于像我一样…...
Python实用工具:pdf转doc
该工具只能使用在英文目录下,且无法转换出图片,以及文本特殊格式。 下载依赖项 pip install PyPDF2 升级依赖项 pip install PyPDF2 --upgrade 查看库版本 python -c "import PyPDF2; print(PyPDF2.__version__)" 下载第二个依赖项 pip i…...
基于Dify实现对Excel的数据分析
在dify部署完成后,大家就可以基于此进行各种应用场景建设,目前dify支持聊天助手(包括对话工作流)、工作流、agent等模式的场景建设,我们在日常工作中经常会遇到各种各样的数据清洗、格式转换处理、数据统计成图等数据分…...
Win全兼容!五五 Excel Word 转 PDF 工具解决多场景转换难题
各位办公小能手们!今天给你们介绍一款超牛的工具——五五Excel Word批量转PDF工具V5.5版。这玩意儿专注搞批量格式转换,能把Excel(.xls/.xlsx)和Word(.doc/.docx)文档唰唰地变成PDF格式。 先说说它的核心功…...
java加强 -Collection集合
集合是一种容器,类似于数组,但集合的大小可变,开发中也非常常用。Collection代表单列集合,每个元素(数据)只包含1个值。Collection集合分为两类,List集合与set集合。 特点 List系列集合&#…...
BGP实验练习1
需求: 要求五台路由器的环回地址均可以相互访问 需求分析: 1.图中存在五个路由器 AR1、AR2、AR3、AR4、AR5,分属不同自治系统(AS),AR1 在 AS 100,AR2 - AR4 在 AS 200,AR5 在 AS …...
Nginx location静态文件映射配置
遇到问题? 以下这个Nginx的配置,愿意为访问https://abc.com会指向一个动态网站,访问https://abc.com/tongsongzj时会访问静态网站,但是配置之后(注意看后面那个location /tongsongzj/静态文件映射的配置)&…...
四、Hive DDL表定义、数据类型、SerDe 与分隔符核心
在理解了 Hive 数据库的基本操作后,本篇笔记将深入到数据存储的核心单元——表 (Table) 的定义和管理。掌握如何创建表、选择合适的数据类型、以及配置数据的读写方式 (特别是 SerDe 和分隔符),是高效使用 Hive 的关键。 一、创建表 (CREATE TABLE)&…...
每日脚本 5.11 - 进制转换和ascii字符
前置知识 python中各个进制的开头 二进制 : 0b 八进制 : 0o 十六进制 : 0x 进制转换函数 : bin() 转为2进制 oct() 转换为八进制的函数 hex() 转换为16进制的函数 ascii码和字符之间的转换 : chr(97) 码转为字符 …...
cookie和session的区别
一、基本概念 1. Cookie 定义:Cookie 是服务器发送到用户浏览器并保存在本地的一小块数据(通常小于4KB),浏览器会在后续请求中自动携带该数据。作用:用于跟踪用户状态(如登录状态)、记…...
Kotlin Multiplatform--03:项目实战
Kotlin Multiplatform--03:项目实战 引言配置iOS开发环境配置项目环境运行程序 引言 本章将会带领读者进行项目实战,了解如何从零开始编译一个能同时在Android和iOS运行的App。开发环境一般来说需要使用Macbook,笔者没试过Windows是否能开发。…...
图形学、人机交互、VR/AR领域文献速读【持续更新中...】
(1)笔者在时间有限的情况下,想要多积累一些自身课题之外的新文献、新知识,所以开了这一篇文章。 (2)想通过将文献喂给大模型,并向大模型提问的方式来快速理解文献的重要信息(如基础i…...
opencascade.js stp vite 调试笔记
Hello, World! | OpenCascade.js cnpm install opencascade.js cnpm install vite-plugin-wasm --save-dev 当你不知道文件写哪的时候trae还是有点用的 ‘’‘ import { defineConfig } from vite; import wasm from vite-plugin-wasm; import rollupWasm from rollup/plugi…...
openharmony系统移植之gpu mesa3d适配
openharmony系统移植之gpu mesa3d适配 文章目录 openharmony系统移植之gpu mesa3d适配1. 环境说明2. gpu内核panfrost驱动2.1 使能panfrost驱动2.2 panfrost dts配置 3. buildroot下测试gpu驱动3.1 buildroot配置编译 4. ohos下mesa3d适配4.1 ohos下mesa3d编译调试4.1.2 编译4.…...
Java开发经验——阿里巴巴编码规范经验总结2
摘要 这篇文章是关于Java开发中阿里巴巴编码规范的经验总结。它强调了避免使用Apache BeanUtils进行属性复制,因为它效率低下且类型转换不安全。推荐使用Spring BeanUtils、Hutool BeanUtil、MapStruct或手动赋值等替代方案。文章还指出不应在视图模板中加入复杂逻…...
Linux中常见开发工具简单介绍
目录 apt/yum 介绍 常用命令 install remove list vim 介绍 常用模式 命令模式 插入模式 批量操作 底行模式 模式替换图 vim的配置文件 gcc/g 介绍 处理过程 预处理 编译 汇编 链接 库 静态库 动态库(共享库) make/Makefile …...
深入理解深度Q网络DQN:基于python从零实现
DQN是什么玩意儿? 深度Q网络(DQN)是深度强化学习领域里一个超厉害的算法。它把Q学习和深度神经网络巧妙地结合在了一起,专门用来搞定那些状态空间维度特别高、特别复杂的难题。它展示了用函数近似来学习价值函数的超能力…...
使用lldb看看Rust的HashMap
目录 前言 正文 读取桶的状态 获取键值对 键值对的指针地址 此时,读取数据 读取索引4的键值对 多添加几个键值对 使用i32作为键,&str作为值 使用i32作为键,String作为值 前言 前面使用ldb看了看不同的类型,这篇再使用…...
Vue3简易版购物车的实现。
文章目录 一、话不多说,直接上代码? 一、话不多说,直接上代码? <template><div><input type"text" placeholder"请输入内容" v-model"keywords"><button click"addGood…...
比亚迪全栈自研生态的底层逻辑
比亚迪全栈自研生态的底层逻辑:汽车工程师必须理解的闭环技术革命 引言:当技术壁垒成为护城河 2023年比亚迪销量突破302万辆的震撼数据背后,隐藏着一个更值得工程师深思的事实:其全栈自研体系覆盖了新能源汽车83%的核心零部件。这…...
[Java实战]Spring Boot 快速配置 HTTPS 并实现 HTTP 自动跳转(八)
[Java实战]Spring Boot 快速配置 HTTPS 并实现 HTTP 自动跳转(八) 引言 在当今网络安全威胁日益严峻的背景下,为 Web 应用启用 HTTPS 已成为基本要求。Spring Boot 提供了简单高效的方式集成 HTTPS 支持,无论是开发环境测试还是生产环境部署࿰…...
5.1.1 WPF中Command使用介绍
WPF 的命令系统是一种强大的输入处理机制,它比传统的事件处理更加灵活和可重用,特别适合 MVVM (Model, View, ViewModel)模式开发。 一、命令系统核心概念 1.命令系统基本元素: 命令(Command): 即ICommand类,使用最多的是RoutedCommand,也可以自己继承ICommand使用自定…...
设计模式简述(十九)桥梁模式
桥梁模式 描述基本组件使用 描述 桥梁模式是一种相对简单的模式,通常以组合替代继承的方式实现。 从设计原则来讲,可以说是单一职责的一种体现。 将原本在一个类中的功能,按更细的粒度拆分到不同的类中,然后各自独立发展。 基本…...
常用设计模式
一、什么是设计模式 设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类编目的代码设计经验总结,旨在解决面向对象设计中反复出现的问题,提升代码的可重用性、可理解性和可靠性。以下从多个维度详细讲解ÿ…...
20242817-李臻-课下作业:Qt和Sqlite
实验内容 阅读附件内容,编译运行附件中第一章,第三章的例子。 实验过程 第一章 t1实践 #include <QApplication> #include <QWidget> #include <QPushButton> #include <QVBoxLayout>int main(int argc, char *argv[]) {QA…...
嵌入式机器学习平台Edge Impulse图像分类 – 快速入门
陈拓 2025/05/08-2025/05/11 1. 简介 官方网址 https://edgeimpulse.com/ 适用于任何边缘设备的人工智能: Gateways - 网关 Sensors & Cameras - 传感器和摄像头 Docker Containers - Docker容器 MCUs, NPUs, CPUs, GPUs 构建数据集、训练模型并优化库以…...
JavaWeb, Spring, Spring Boot
出现时间 JavaWeb - Spring - Spring Boot 一、JavaWeb 的发展历程 Servlet 和 JSP: Servlet:1997 年首次发布,用于处理 HTTP 请求和响应。 JSP:1999 年首次发布,用于动态生成 HTML 页面。 特点:提供了基…...
upload-labs靶场通关详解:第五关
一、分析源代码 $is_upload false; $msg null; if (isset($_POST[submit])) {if (file_exists(UPLOAD_PATH)) {$deny_ext array(".php",".php5",".php4",".php3",".php2",".html",".htm",".ph…...
【问题】Watt加速github访问速度:好用[特殊字符]
前言 GitHub 是全球知名的代码托管平台,主要用于软件开发,提供 Git 仓库托管、协作工具等功能,经常要用到,但是国内用户常因网络问题难以稳定访问 。 Watt Toolkit(原名 Steam)是由江苏蒸汽凡星科技有限公…...
GitHub打开缓慢甚至失败的解决办法
在C:\Windows\System32\drivers\etc的hosts中增加如下内容: 20.205.243.166 github.com 199.59.149.236 github.global.ssl.fastly.net185.199.109.153 http://assets-cdn.github.com 185.199.108.153 http://assets-cdn.github.com 185.199.110.153 http://asset…...
【25软考网工】第六章(3)数字签名和数字证书
博客主页:christine-rr-CSDN博客 专栏主页:软考中级网络工程师笔记 大家好,我是christine-rr !目前《软考中级网络工程师》专栏已经更新二十多篇文章了,每篇笔记都包含详细的知识点,希望能帮助到你!…...
Android Native 函数 Hook 技术介绍
版权归作者所有,如有转发,请注明文章出处:https://cyrus-studio.github.io/blog/ 前言 Android Native 函数 Hook 技术是一种在应用运行时拦截或替换系统或自身函数行为的手段,常见实现包括 PLT Hook、Inline Hook。 PLT Hook 和…...
代码随想录算法训练营第60期第三十二天打卡
大家好,今天是我们贪心算法章节的第三阶段,前面我们讲过的几道题不知道大家理解的情况如何,还是那句话,贪心算法没有固定的套路与模板,一道题一个思路,我们要多思考这样慢慢地我就就可以水到渠成。今天我们…...