JUC并发编程——JUC并发编程概述及Lock锁(重点)(基于狂神说的学习笔记)
基于bilibili狂神说JUC并发编程视频所做笔记
概述
什么是JUC
JUC时java.util工具包中的三个包的简称
java.util.concurrent
java.util.concurrent.atomic
java.util.concurrent.locks
业务:普通的线程代码中,我们常使用Runnable接口
但Runnable没有返回值,且效率相比较于Callable来说相对较低,功能也没有Callable强大
线程和进程
进程:相当于一个程序
一个进程当中往往可以包含多个线程,且至少包含一个线程
Java默认有2个线程:mian(主线程),GC(垃圾回收)
Java真的可以开启线程吗?
java是无法开启线程的,Java运行在JVM(虚拟机)之上,无法直接操作硬件,因此其实际上是无法开启线程的,在我们无论使用Runnable接口还是继承Thread,用start()方法开启线程,其本质上都是调用==private native void start0()==方法,而该方法是本地方法,是运行底层的C++
并发编程:
并发与并行:
并发:(多个线程同时操作一个核)
- CPU一核,快速交替轮换,模拟多核
并行:(多个线程操作多个核)
- CPU多核,可以多个线程同时执行;引入线程池的概念
查看自己CPU核数:
public class Test1 {public static void main(String[] args) {// 获取CPU的核数// COU 密集型,IO密集型System.out.println(Runtime.getRuntime().availableProcessors());}
}
并发编程的本质:充分利用CPU的资源
线程有几个状态
答: 6个,分别为:NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED
public enum State{/*** Thread state for a thread which has not yet started.*/
NEW,/*** Thread state for a runnable thread. A thread in the runnable* state is executing in the Java virtual machine but it may* be waiting for other resources from the operating system* such as processor.*/
RUNNABLE,/*** Thread state for a thread blocked waiting for a monitor lock.* A thread in the blocked state is waiting for a monitor lock* to enter a synchronized block/method or* reenter a synchronized block/method after calling* {@link Object#wait() Object.wait}.*/
BLOCKED,/*** Thread state for a waiting thread.* A thread is in the waiting state due to calling one of the* following methods:* <ul>* <li>{@link Object#wait() Object.wait} with no timeout</li>* <li>{@link #join() Thread.join} with no timeout</li>* <li>{@link LockSupport#park() LockSupport.park}</li>* </ul>** <p>A thread in the waiting state is waiting for another thread to* perform a particular action.** For example, a thread that has called {@code Object.wait()}* on an object is waiting for another thread to call* {@code Object.notify()} or {@code Object.notifyAll()} on* that object. A thread that has called {@code Thread.join()}* is waiting for a specified thread to terminate.*/
WAITING,/*** Thread state for a waiting thread with a specified waiting time.* A thread is in the timed waiting state due to calling one of* the following methods with a specified positive waiting time:* <ul>* <li>{@link #sleep Thread.sleep}</li>* <li>{@link Object#wait(long) Object.wait} with timeout</li>* <li>{@link #join(long) Thread.join} with timeout</li>* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>* </ul>*/
TIMED_WAITING,/*** Thread state for a terminated thread.* The thread has completed execution.*/
TERMINATED;
}
WAITING 与 TIMED_WAITING的区别为:
WAITING会一直等待唤醒,或其他线程,资源的响应
TIMED_WAITING为超时等待,一旦时间到,则不再等待
wait/sleep的区别
- 来自不同的类
wait 来自Object类
sleep来自Thread类
- 关于锁的释放
wait会释放锁
sleep不会释放锁,抱着锁睡觉
- 使用的范围是不同的
wait 必须在同步代码块中使用
sleep 可以在任何地方睡
- 是否需要捕获异常
wait不需要捕获异常
sleep必须要捕获异常
(但是,只要与线程有关的操作,都要捕获中断异常)
Lock锁(重点)
回顾synchronized
传统Synchronized
package syn;// OOP并发编程
public class SaleTicketDemo01 {public static void main(String[] args) {// 声明一个票对象,使3个线程可以调用买票方法Ticket ticket = new Ticket();// 使用lambda表达式,回顾:lambda表达式是一种极简的表达艺术,但仅用于函数式接口new Thread(()->{for (int i = 0; i < 40; i++) {ticket.sale();}},"A").start();new Thread(()->{for (int i = 0; i < 40; i++) {ticket.sale();}},"B").start();new Thread(()->{for (int i = 0; i < 40; i++) {ticket.sale();}},"C").start();}
}// 高耦合,对象及为对象,不要附加多余功能,不要将其变成线程类
class Ticket{// 属性、方法private int ticketNums = 30;// synchronized 本质就是锁,队列public synchronized void sale(){if (ticketNums > 0) {System.out.println(Thread.currentThread().getName()+" sales "+ticketNums--+",and remains"+ticketNums);}}
}
Lock锁实现线程安全示例
java.util.concurrent.locks下有三个接口
- Condition
- Lock
- ReadWriteLock
Lock接口
实现类:
- ReentranLock(可重入锁)
- ReentrantReadWriteLock.ReadLock(读锁)
- ReentrantReadWriteLock.WriteLock(写锁)
在ReentrantLock中,其构造函数:
公平锁:先到先得
非公平锁(默认):可以插队,看CPU调度
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class LockDemo02 {public static void main(String[] args) {// 声明一个票对象,使3个线程可以调用买票方法Ticket ticket = new Ticket();// 使用lambda表达式,回顾:lambda表达式是一种极简的表达艺术,但仅用于函数式接口new Thread(()->{for (int i = 0; i < 40; i++) ticket.sale();},"A").start();new Thread(()->{for (int i = 0; i < 40; i++) ticket.sale();},"B").start();new Thread(()->{for (int i = 0; i < 40; i++) ticket.sale();},"C").start();}
}// 高耦合,对象及为对象,不要附加多余功能,不要将其变成线程类
// 使用lock锁
/*
Lock三部曲
1、 new ReentrantLock()
2、 lock.lock()
3、 finally => lock.unlock()*/
class Ticket{// 属性、方法private int ticketNums = 30;Lock lock = new ReentrantLock();// synchronized 本质就是锁,队列public void sale(){lock.lock();// 加锁try {//业务代码if (ticketNums > 0) {System.out.println(Thread.currentThread().getName()+" sales "+ticketNums--+",and remains"+ticketNums);}} catch (Exception e) {throw new RuntimeException(e);} finally {lock.unlock();// 解锁}}
}
Lock与synchronized的区别
1、synchronized内置的Java关键字;而Lock是一个Java类
2、synchronized无法判断获取锁的状态;Lock可以判断是否获取到了锁
3、synchronized会自动释放锁;Lock必须手动释放锁,如果不释放锁,则会造成死锁
4、synchronized线程1(获得锁),线程2(一直等待); Lock锁时,线程2就不一定会等待下去
5、synchronized可重入锁,不可中断的,非公平锁(不可更改);Lock,可重入锁,可以判断锁,默认非公平(可以修改)
6、synchronized适合锁少量的代码同步问题;Lock锁适合锁大量的同步代码
锁是什么,如何判断锁的是谁
看8锁现象!!!
生产者和消费者问题
生产者消费者问题 synchronized版
package PC;/*** 线程之间的通信问题:生产者和消费者问题 等待唤醒 通知唤醒* 线程交替执行 A B 操作同一个变量 num = 0* A num+1* B num-1*/
public class A {public static void main(String[] args) {Data data = new Data();new Thread(()->{for (int i = 0; i < 10; i++) {try {data.increment();} catch (InterruptedException e) {throw new RuntimeException(e);}}},"A").start();new Thread(()->{for (int i = 0; i < 10; i++) {try {data.decrement();} catch (InterruptedException e) {throw new RuntimeException(e);}}},"B").start();}
}// 口诀:等待,业务,通知
// 资源类
class Data{private int number = 0;// +1操作public synchronized void increment() throws InterruptedException {// 等待if (number != 0){this.wait();}// 业务number++;System.out.println(Thread.currentThread().getName()+"=>"+number);// 通知其他线程,我加一完毕this.notifyAll();}// -1操作public synchronized void decrement() throws InterruptedException {// 等待if (number == 0){this.wait();}// 业务number--;System.out.println(Thread.currentThread().getName()+"=>"+number);// 通知其他线程,我减一完毕this.notifyAll();}
}
这真的线程安全吗?如果有四个线程同时跑呢?
答:不安全,当四个线程在跑时,则会出现意料之外的情况
为什么会产生这种情况?
答:造成这种现象的原因是虚假唤醒
什么是虚假唤醒?
多线程环境下,有多个线程执行了wait()方法,需要其他线程执行notify()或者notifyAll()方法去唤醒它们,假如多个线程都被唤醒了,但是只有其中一部分是有用的唤醒操作,其余的唤醒都是无用功;对于不应该被唤醒的线程而言,便是虚假唤醒。
比如:仓库有货了才能出库,突然仓库入库了一个货品;这时所有的线程(货车)都被唤醒,来执行出库操作;实际上只有一个线程(货车)能执行出库操作,其他线程都是虚假唤醒。
参考博客:Java线程虚假唤醒是什么、如何避免?_java 虚假唤醒_秃秃爱健身的博客-CSDN博客
很重要:以下是个人感悟!
其实当初笔者在此处还是很困惑的,为什么虚假唤醒会造成线程同时运行而不顾if条件语句,后来笔者意识到一个很重要的问题:“wait()方法会使线程放弃锁”。也就是说,当A线程拿到了同步锁之后,进入if条件语句判断,如果此时条件为true,它会进入waiting状态并放弃同步锁,因此,C线程在这段时间有可能会乘虚而入,抢在B线程或D线程将A线程唤醒前进入同步代码块,同样进入if语句的waiting状态,之后,B线程或D线程完成其业务逻辑后,执行notifyAll()方法,就会将A线程与C线程同时唤醒,然后两者都会执行业务逻辑,导致一次减,两次加,与我们的预期(我们的逻辑是加一次,减一次)不符。因此线程不安全。
以上解释只是个人猜想,还未曾验证过,比如将wait换成sleep,抱着线程休眠是否会出现同样的问题
如何避免虚假唤醒?
将if条件语句改为while循环语句
当使用if条件语句时,如果线程在if条件语句中被wait中断退出,当其重新进入回到它原本所在的位置后就会发现,它已经进行过判断了,接下来,就算已经有线程抢先一步操作,它也会义无反顾地往下走,因为没有条件能够拦住它啦!
而当我们使用while循环语句会发现:(以下是官方文档所给的推荐代码)
synchronized (obj) { while (<condition does not hold> and <timeout not exceeded>) { long timeoutMillis = ... ; // recompute timeout values int nanos = ... ; obj.wait(timeoutMillis, nanos); } ... // Perform action appropriate to condition or timeout
}
如果线程在while循环中被wait中断退出,当其重新进入回到它原本所在的位置后就会发现,本次循环已经结束,**接下来并不是执行后面的业务代码,而是返回到while循环开头,重新判断一次是否满足条件。**这样的操作就保证了即使在退出重进,也会进行再一次的判断确保线程安全。
以下为示例中被修改的代码片段:
public synchronized void increment() throws InterruptedException {// 等待// ***************** 此处的if被改为while **********************while (number != 0){this.wait();}// 业务number++;System.out.println(Thread.currentThread().getName()+"=>"+number);// 通知其他线程,我加一完毕this.notifyAll();
}// -1操作
public synchronized void decrement() throws InterruptedException {// 等待// ***************** 此处的if被改为while **********************while (number == 0){this.wait();}// 业务number--;System.out.println(Thread.currentThread().getName()+"=>"+number);// 通知其他线程,我减一完毕this.notifyAll();
}
生产者消费者问题 JUC版
对应于synchronized,JUC版本下,Lock锁也有对应的唤醒与停止方法,分别是condition接口下的signal()与await()
以下是官方文档的描述:
示例代码如下:
package PC;import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class B {public static void main(String[] args) {Data2 data = new Data2();new Thread(()->{for (int i = 0; i < 10; i++) {try {data.increment();} catch (InterruptedException e) {throw new RuntimeException(e);}}},"A").start();new Thread(()->{for (int i = 0; i < 10; i++) {try {data.decrement();} catch (InterruptedException e) {throw new RuntimeException(e);}}},"B").start();new Thread(()->{for (int i = 0; i < 10; i++) {try {data.increment();} catch (InterruptedException e) {throw new RuntimeException(e);}}},"C").start();new Thread(()->{for (int i = 0; i < 10; i++) {try {data.decrement();} catch (InterruptedException e) {throw new RuntimeException(e);}}},"D").start();}
}// 口诀:等待,业务,通知
// 资源类
class Data2{private int number = 0;Lock lock = new ReentrantLock();// +1操作public void increment() throws InterruptedException {Condition condition = lock.newCondition();/*** condition.await(); 等待* condition.signalAll(); 唤醒全部*/try {lock.lock();// =============== 业务代码 ==================// 等待while (number != 0){condition.await();}// 业务number++;// 通知其他线程,我加一完毕System.out.println(Thread.currentThread().getName()+"=>"+number);// ===========================================} catch (Exception e) {throw new RuntimeException(e);}finally{lock.unlock();}}// -1操作public void decrement() throws InterruptedException {Condition condition = lock.newCondition();try {lock.lock();// =============== 业务代码 ==================// 等待while (number == 0){}// 业务number--;// 通知其他线程,我减一完毕System.out.println(Thread.currentThread().getName()+"=>"+number);// ===========================================} catch (Exception e) {throw new RuntimeException(e); } finally {lock.unlock();}}
}
Condition 的优势在哪里
可以实现精准的通知和唤醒线程
以下示例实现精准唤醒线程,在A线程执行完后精准唤醒B线程执行,B线程执行完后精准唤醒C线程执行,C线程执行完后精准唤醒A线程执行
package PC;import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;/*** A 执行完调用 B,B 执行完调用 C,C 执行调用 A*/
public class C {public static void main(String[] args) {// new 资源类Data3 data = new Data3();// 创建线程并执行线程new Thread(()->{for (int i = 0; i < 5; i++) data.printA();},"A").start();new Thread(()->{for (int i = 0; i < 5; i++) data.printB();},"B").start();new Thread(()->{for (int i = 0; i < 5; i++) data.printC();},"C").start();}
}//资源类
class Data3{private Lock lock = new ReentrantLock();private Condition conditionA = lock.newCondition();private Condition conditionB = lock.newCondition();private Condition conditionC = lock.newCondition();private int number = 1; // 若number=1则A执行,若number=2则B执行,若number=3则C执行public void printA(){lock.lock();try {// 业务, 判断->执行->通知while(number != 1){conditionA.await();}System.out.println(Thread.currentThread().getName()+" now is AAAAA time!");// 唤醒指定的线程Bnumber ++;conditionB.signal();} catch (Exception e) {throw new RuntimeException(e);} finally {lock.unlock();}}public void printB(){lock.lock();try {while(number != 2){conditionB.await();}System.out.println(Thread.currentThread().getName()+"now is BBBBB time!");// 唤醒指定线程Cnumber++;conditionC.signal();} catch (Exception e) {throw new RuntimeException(e);} finally {lock.unlock();}}public void printC(){lock.lock();try {while(number != 3){conditionC.await();}System.out.println(Thread.currentThread().getName()+"now is CCCCC time!");number = 1;conditionA.signal();} catch (Exception e) {throw new RuntimeException(e);} finally {lock.unlock();}}}
总结:
- Lock锁与synchronized锁的区别要分清
- 在多线程情况下,不要使用if条件语句来判断是否wait或await某一线程,要用while循环语句来判断,否则线程不安全
- Lock锁下,使用await与signal等价于wait和notify,但有所区别,Lock锁更加灵活和可以精准唤醒某些线程
相关文章:

JUC并发编程——JUC并发编程概述及Lock锁(重点)(基于狂神说的学习笔记)
基于bilibili狂神说JUC并发编程视频所做笔记 概述 什么是JUC JUC时java.util工具包中的三个包的简称 java.util.concurrent java.util.concurrent.atomic java.util.concurrent.locks 业务:普通的线程代码中,我们常使用Runnable接口 但Runnable没有返…...

深入了解 Java 中的时间信息定义、转换、比较和操作
1. 简介 在过去的传统Java日期处理中,经常面临着一些问题。比如,java.util.Date和java.util.Calendar在表示日期和时间时存在着一些奇怪的行为,如月份从0开始计数、对日期进行格式化的方式繁琐不直观等。这些问题给开发带来了一定的困扰。 …...

2023年中国智能矿山发展历程及趋势分析:智能矿山健康有序发展[图]
智能矿山系统对矿山生产提质增效的效果已经开始显现:对不合规、有风险的行动进行及时预警,减少安全事故发生概率,避免因停产整顿产生的巨额亏损;精细化管理整个生产流程,避免过往传统粗放的流程导致的浪费,…...

acwing算法基础之基础算法--整数离散化算法
目录 1 知识点2 模板 1 知识点 整个范围很大,但存在的数据点很少。比如从 − 1 0 9 -10^9 −109到 1 0 9 10^9 109,但总共只有 1 0 6 10^6 106个数。 可以采用离散化的思想来做,即将离散的大数值映射成连续的小数值(一般是 1 , …...

基于SSM框架的安全教育平台
末尾获取源码 开发语言:Java Java开发工具:JDK1.8 后端框架:SSM 前端:Vue 数据库:MySQL5.7和Navicat管理工具结合 服务器:Tomcat8.5 开发软件:IDEA / Eclipse 是否Maven项目:是 目录…...

Kafka生产者使用案例
1.生产者发送消息的过程 首先介绍一下 Kafka 生产者发送消息的过程: 1)Kafka 会将发送消息包装为 ProducerRecord 对象, ProducerRecord 对象包含了目标主题和要发送的内容,同时还可以指定键和分区。在发送 ProducerRecord 对象前,…...

EasyX图形库实现贪吃蛇游戏
⭐大家好,我是Dark Falme Masker,学习了动画制作及键盘交互之后,我们就可以开动利用图形库写一个简单的贪吃蛇小游戏,增加学习乐趣。 ⭐专栏:EasyX部分小游戏实现详细讲解 最终效果如下 首先包含头文件 #include<stdio.h> #…...

利用 Amazon CodeWhisperer 激发孩子的编程兴趣
我是一个程序员,也是一个父亲。工作之余我会经常和儿子聊他们小学信息技术课学习的 Scratch 和 Kitten 这两款图形化的少儿编程工具。 我儿子有一次指着书房里显示器上显示的 Visual Studio Code 问我,“为什么我们上课用的开发界面,和爸爸你…...

2023年中国分子筛稀土催化材料竞争格局及行业市场规模分析[图]
稀土催化材料能够起到提高催化剂热稳定性、催化剂活性、催化剂储氧能力,以及减少贵金属活性组分用量等作用,广泛应用于石油化工、汽车尾气净化、工业废气和人居环境净化、燃料电池等领域。 2015-2023年中国稀土催化材料规模及预测 资料来源:…...

vue3插件——vue-web-screen-shot——实现页面截图功能
最近在看前同事发我的vue3框架时,发现他们有个功能是要实现页面截图功能。 vue3插件——vue-web-screen-shot——实现页面截图功能 效果图如下:1.操作步骤1.1在项目中添加vvue-web-screen-shot组件1.2在项目入口文件导入组件——main.ts1.3在需要使用的页…...

简单总结Centos7安装Tomcat10.0版本
文章目录 前言JDK8安装部署Tomcat 前言 注意jdk与tomcat的兼容问题,其他的只要正确操作一般问题不大 Tomcat 是由 Apache 开发的一个 Servlet 容器,实现了对 Servlet 和 JSP 的支持,并提供了作为Web服务器的一些特有功能,如Tomca…...

ffmpeg中AVCodecContext和AVCodec的关系分析
怎么理解AVCodecContext和AVCodec的关系 AVCodecContext和AVCodec是FFmpeg库中两个相关的结构体,它们在音视频编解码中扮演着不同的角色。 AVCodecContext:是编解码器上下文结构体,用于存储音视频编解码器的参数和状态信息。它包含了进行音视…...

2023年中国门把手产量、销量及市场规模分析[图]
门把手行业是指专门从事门把手的设计、制造、销售和安装等相关业务的行业。门把手是门窗装饰硬件的一种,用于开启和关闭门窗,同时也具有装饰和美化门窗的作用。 门把手行业分类 资料来源:共研产业咨询(共研网) 随着消…...

HTML 核心技术点基础详细解析以及综合小案例
核心技术点 网页组成 排版标签 多媒体标签及属性 综合案例一 - 个人简介 综合案例二 - Vue 简介 02-标签语法 HTML 超文本标记语言——HyperText Markup Language。 超文本:链接 标记:标签,带尖括号的文本 标签结构 标签要成…...

BAT学习——批处理脚本(也称为BAT文件)常用语法元素与命令
批处理脚本(也称为BAT文件)使用Windows的批处理语言编写,它具有一些常用的语法元素和命令。以下是一些BAT编程的常用语法元素和命令: 命令行命令: 批处理脚本通常包含一系列Windows命令,例如echo࿰…...

AMD AFMF不但能用在游戏,也适用于视频
近期AMD发布了AMD Software Adrenalin Edition预览版驱动程序,增加了对平滑移动帧(AMD Fluid Motion Frames,AFMF)功能的支持,也就是AMD的“帧生成”技术,与DLSS 3类似,作为FidelityFX Super Re…...

CSS 常用样式浮动属性
一、概述 CSS 中,浮动属性的作用是让元素向左或向右浮动,使其他元素围绕它排布,常用的浮动属性有以下几种: float: left; 使元素向左浮动,其他元素从右侧包围它。 float: right; 使元素向右浮动,其他元素…...

Linux引导故障排除:从问题到解决方案的详细指南
1 BIOS初始化 通电->对硬件检测->初始化硬件时钟 2 磁盘引导及其修复 2.1 磁盘引导故障 磁盘主引导记录(MBR)是在0磁道1扇区位置,446字节。 MBR作用:记录grub2引导文件的位置 2.2 修复 步骤:1、光盘进…...

【vim 学习系列文章 6 -- vim 如何从上次退出的位置打开文件】
文章目录 1.1 vim 如何从上次退出的位置打开文件1.2 autogroup 命令学习1.2.1 augroup 基本语法 1.3 vim call 命令详细介绍 1.1 vim 如何从上次退出的位置打开文件 假设我打开了文件 test.c,然后我向下滚动到第 50 行,然后我做了一些修改并关闭了文件。…...

怎样学习C#上位机编程?
怎样学习C#上位机编程? 00001. 掌握C#编程和.NET框架基础。 00002. 学WinForm应用开发,了解控件使用和事件编程。 00003. 熟悉基本数据结构和算法,如链表、栈、队列。 00004. 理解串口通信协议和方法,用于与硬件交互。 00005…...

【算法-动态规划】两个字符串的删除操作-力扣 583
💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kuan 的首页,持续学…...

【06】基础知识:typescript中的泛型
一、泛型的定义 在软件开发中,我们不仅要创建一致的定义良好的API,同时也要考虑可重用性。 组件不仅能支持当前数据类型,同时也能支持未来的数据类型,这在创建大型系统时提供了十分灵活的功能。 在像 C# 和 Java 这样的语言中&…...

flutter 绘制原理探究
文章目录 Widget1、简介2、源码分析Element1、简介2、源码分析RenderObjectWidget 渲染过程总结思考Flutter 的核心设计思想便是“一切皆 Widget”,Widget 是 Flutter 功能的抽象描述,是视图的配置信息,同样也是数据的映射,是 Flutter 开发框架中最基本的概念。 在 Flutter…...

[Java]SPI扩展功能
一、什么是SPI Java SPI(Service Provider Interface)是Java官方提供的一种服务发现机制。 它允许在运行时动态地加载实现特定接口的类,而不需要在代码中显式地指定该类,从而实现解耦和灵活性。 二、实现原理 基于 Java 类加载…...

机器人命令表设计
演算命令 CLEAR 将数据 1 上被指定的编号以后的变数的内容,以及数据 2 上仅被指定的个数都清除至 0。 INC 在被指定的变数内容上加上 1。 DEC 在被指定的变数内容上减掉 1。 SET 在数据 1 上设定数据 2。 ADD 将数据 1 和数据 2 相加,得出的结果保存在数…...

STM32--WDG看门狗
文章目录 WDG简介IWDGIWDG的超时计算WWDGWWDG超时和窗口值设定独立看门狗工程WWDG工程 WDG简介 WDG看门狗(Watchdog Timer)是一种常见的硬件设备,在STM32F10系列中,有两种看门狗,分别是独立看门狗和窗口看门狗&#x…...

(※)力扣刷题-字符串-实现 strStr()(KMP算法)
28 实现 strStr() 实现 strStr() 函数。 给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回 -1。 示例 1: 输入: haystack “hello”, needle “ll” 输出: 2 示例…...

Redis 集群 Redis 事务 Redis 流水线 Redis 发布订阅 Redis Lua脚本操作
Redis 集群 & Redis 事务 & Redis 流水线 & Redis 发布订阅 Redis 集群linux安装redis主从配置查看当前实例主从信息 Redis Sentinelsentinel Redis Cluster Redis 事务Redis 流水线Redis 发布订阅Redis Lua脚本操作 Redis 集群 linux安装redis 下载安装包&#…...

【算法与数据结构】--常见数据结构--栈和队列
一、栈 栈(Stack) 是一种基本的数据结构,具有后进先出(LIFO)的特性,类似于现实生活中的一叠盘子。栈用于存储一组元素,但只允许在栈顶进行插入(入栈)和删除(…...

Linux shell编程学习笔记11:关系运算
Linux Shell 脚本编程和其他编程语言一样,支持算数、关系、布尔、字符串、文件测试等多种运算。前面几节我们研究了 Linux shell编程 中的 字符串运算 和 算术运算,今天我们来研究 Linux shell编程中的的关系运算。 一、关系运算符功能说明 运算符说明…...