当前位置: 首页 > news >正文

Java中的多线程和线程安全问题

线程

线程是操作系统进行调度的最小单位。一个进程至少包含一个主线程,而一个线程可以启动多个子线程。线程之间共享进程的资源,但也有自己的局部变量。多线程程序和普通程序的区别:每个线程都是一个独立的执行流;多个线程之间是并发执行的。

在这里插入图片描述

多线程的实现方法

继承Thread类

class MyThread extends Thread {public MyThread(String name) {super(name);}public MyThread() {}@Overridepublic void run() {for (int i = 0; i < 50; i++) {System.out.println(this.getName() + ":" + i);}}
}public class Text {public static void main(String[] args) {//实例化对象  创建线程MyThread t1 = new MyThread();MyThread t2 = new MyThread("线程t2");t1.start();t2.start();  //启动线程for (int i = 0; i < 5; i++) {System.out.println("hello main");}}
}

使用lambda表达式创建线程

public class Demo1 {public static void main(String[] args) {Thread t1 = new Thread(() -> {while (true) {System.out.println("t1线程");}});Thread t2 = new Thread(() -> {while (true) {System.out.println("t2线程");}});t1.start();t2.start();while (true) {System.out.println("hello main");}}
}

start()和run()的区别

start()是启动一个分支线程 是一个专门用来启动线程的方法 而run()是一个普通方法和main方法是同级别的 单纯调用run方法是不会启动多线程并发执行的.

public class Demo2 {public static void main(String[] args) {Thread thread = new Thread(() -> {for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName()+ ":我还存活");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println(Thread.currentThread().getName() + ":我即将死去");});System.out.println(Thread.currentThread().getName() + ": 状态" + thread.getState());//启动线程thread.start();boolean flg = thread.isAlive();  //是否存活System.out.println(Thread.currentThread().getName() + ": 状态:" + thread.getState());}
}

上面的代码涉及到的一些线程的方法
在这里插入图片描述
还有获取线程状态的方法是getState()

sleep()方法

该方法就是让线程休眠 括号里面写休眠的时间 单位是毫秒级别 下面通过代码来描述

public class Demo3 {public static void main(String[] args) {Thread t1 = new Thread(() -> {for (int i = 0; i < 5; i++) {System.out.println("hello t1");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});Thread t2 = new Thread(() -> {for (int i = 0; i < 5;i++) {System.out.println("hello t2");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});t1.start();t2.start();}
}

通过上面的代码 就可以控制两个线程没执行一遍就休眠一秒钟 再继续执行下一遍。

join方法

多线程的join方法就是用来等待其他线程的方法 就是谁调用该方法谁就等待 join()这种是死等 ,当然也可以有时间的等 过了这个时间就不等了
就类似舔狗 有写舔狗 舔自己的女神 可能 会一直舔 舔到死 那种 有些就是有原则的舔 ,可能就在固定的时间内舔 ,过了这个时间段就坚决不舔。

代码实现如下:

public class Demo4 {public static void main(String[] args) throws InterruptedException {Thread t2 = new Thread(() -> {for (int i = 0; i < 5; i++) {System.out.println("t2");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});Thread t1 = new Thread(() -> {//t1等t2try {Thread.sleep(500);t2.join();} catch (InterruptedException e) {throw new RuntimeException(e);}for (int i = 0; i < 5; i++) {System.out.println("t1");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});t1.start();t2.start();t1.join();System.out.println("main end");}
}
import java.time.Year;public class Demo5 {public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {for (int i = 0; i < 5; i++) {System.out.println("t1线程在执行");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});t1.start();t1.join(3000);System.out.println("main线程over");}
}

运行结果:
在这里插入图片描述
通过代码和运行截图可以看出 main线程在等了3秒之后就结束了 而t1线程还没执行完。
join方法的底层原理:
join方法的工作原理基于Java的内置锁机制。当你调用join方法时,当前线程会尝试获取目标线程对象的锁,并在目标线程执行完毕后释放锁。在这个过程中 ,当前调用的线程就会被阻塞等待,直到目标线程结束执行并释放锁,当前线程才能执行。

线程的状态

在Java官方的线程状态分类中 ,一共给出6种线程状态。
在任意一个时间点 ,一个线程就有且仅有一种状态

6种状态如下

NEW:创建好线程但是还没启动

RUNNABLE该状态是已经调用了start()方法 是可以工作的状态 但这种状态的线程有两种情况 :一种是正在执行 还要一种就是在等待CPU分配执行时间。

BLOCKED: 该状态就是线程被阻塞了,在等待别的线程的锁

WAITING:这种状态就是无限期等待 CPU不会给他分配执行时间 这种要等别的线程来唤醒。

TINED_WAITING: 这种就是有限期德等待,在一定时间之后就会被系统自动唤醒。

TERMINATED:工作完成了 线程已经执行结束了 。

线程状态的转换

在这里插入图片描述

线程的安全问题

那什么叫做线程安全呢
就是多线程环境下代码运行的结果是符合问你预期的,即在单线程环境应该的结果,则表示该线程是安全的 ,否则该线程就是不安全的。

线程不安全的原因

线程的调度是随机的 这是线程不安全的罪魁祸首
随机调度使一个程序在多线程环境下,执行顺序存在很多变数
多线程是一个神奇的东西

下面我们通过一个代码来看看什么是线程的不安全 也就是有bug 和预期效果不符。

public class Demo6 {private static int count = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {count++;}});Thread t2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {count++;}});t1.start();t2.start();t1.join();t2.join();System.out.println("count = " + count);}
}

运行结果:
在这里插入图片描述
在这里插入图片描述
结果不符合预期 预期结果是100000 但是却输出小于10万的数,且每次运行的结果都不一样 为什么呢?
1、count++这个操作,站在cpu指令的角度来说,其实是三个指令。
load :把内存中的数据,加载到寄存器中
add:把寄存器中的值 + 1
save :把寄存器中的值写回到内存中

2、两个线程并发执行的进行count++
因为多线程的执行是随机调度,抢占式执行的模式

相当于某个线程执行指令的过程中,当她执行到任何一个指令的时候都有可能被其他线程把他的cpu资源抢占走。

综上所述,实际并行执行的时候,两个线程执行指令的相对顺序就可能存在无数种可能。

在这里插入图片描述
除了上面的两种可能还有无数种可能。

出现线程不安全的原因:
还是那句话:1、线程在系统中是随机调度的

2、在上面那个代码中 ,多个线程同时修改同一个变量就会出现这种线程不安全的问题

3、线程针对变量的修改操作,不是“原子”的
就像上面count++这种代码 就不是原子操作 因为该操作涉及到三个指令。
但有些操作,虽然也是修改操作 ,但是只有一个指令,是原子的。
比如直接针对 int / double进行赋值操作(在cpu上就只有一个move操作)
相当于来说 ,就是某个代码操作对应到一个cpu指令就是原子的 如果是多个那就是原子的。

那如何解决 该问题呢
那必须得从原因入手

线程调度是随机的这个我们无法干预
我们可以通过一些操作 把上诉非原子操作,打包成一个原子操作
那就是给线程加锁 下面我们举个例子:
就比如上厕所 现在厕所里面就一个坑位 现在来了两个人A和B 现在A先进去上厕所了 结果 A还没上完 B就冲了进去 这显然是不科学的 。在现实生活中,一般我们上厕所都会锁上门 。A进去上厕所把门给锁上,这时B要是也想进去上厕所就得等待A上完厕所解锁出来 这时B才能进去接着上厕所。

:本质上也是操作系统提供的功能 通过api给到应用程序 ;在Java中JVM对于这样的操作系统又进行了封装。

synchronized对象锁(可重入锁)

在Java中 我们引入synchronized 关键字
synchronized()括号里面就是写锁的对象

锁对象的用途,有且仅有一个 ,就是用来区分 两个线程是否针对同一个对象加锁
如果是 那就会出现锁竞争 /互斥 就会引起阻塞等待
如果不是,就不会出现锁竞争 ,也就不会出现阻塞。

下面给你们看看加上锁之后的代码

public class Demo6 {private static int count = 0;public static void main(String[] args) throws InterruptedException {Object locker = new Object();Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {synchronized (locker) {count++;}}});Thread t2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {synchronized (locker) {count++;}}});t1.start();t2.start();t1.join();t2.join();System.out.println("count = " + count);}
}

运行的结果:
在这里插入图片描述
加锁之后 明显线程就没有bug了变安全了

加上锁之后 当t1进入count操作的时候 ,如果t2想进去执行 就会阻塞等待 因为 现在锁在t1的手里

还有 一种嵌套加锁 就是在第一个锁的基础上再加一个锁 就相当于 你要获取第二个锁得先执行完第一个锁 要想执行完第一个锁 ,得获取到第二个锁 ,这就相互矛盾了 就产生死锁看了

但是实际上 对于synchronized是不适用的 这个锁在上面这种情况下不会出现死锁 但是这种情况在C++和Python中就会出现死锁。

synchronized没有出现上诉情况是因为自己内部进行了特殊的处理(JVM)
每个锁对象里,会记录当前是哪个线程持有这个锁。
当针对这个对象加锁操作时,就会先判定一下,当前尝试加锁线程是否是持有锁的线程
如果不是就阻塞 否则就直接放行 不会阻塞。

场景二: ;两个线程 两把锁
现在有线程t1 和t2 以及锁A和锁B 现在这两个线程都需要获取这两把锁 ‘拿到锁A后 不释放锁A ,继续去获取锁B 就相当于 先让两个线程分别拿到一把锁,然后去尝试获取对方的锁。

举个例子: 疫情期间,现在广东的健康吗崩了 程序猿赶紧来到公司准备修bug 被保安拦住了
保安: 请出示健康吗 才能上楼
程序猿:我得上楼修复bug才能出示健康码

就这样 如果两个人互不相让 就会出现僵住的局面。
类似的 还有 钥匙锁在车里 ,而车钥匙锁屋子里了 这样也是僵住了

下面我们通过代码来实现一下 该情况:

public class Demo8 {public static void main(String[] args) throws InterruptedException {Object locker1 = new Object();Object locker2 = new Object();Thread t1 = new Thread(() -> {synchronized (locker1) {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker2) {System.out.println("t1获取到两把锁");}}});Thread t2 = new Thread(() -> {synchronized (locker2) {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker1) {System.out.println("t2获取到两把锁");}}});t1.start();t2.start();t1.join();t2.join();}
}

运行结果:
在这里插入图片描述
你会发现现在运行起来什么都没有
t1尝试针对locker2加锁 就会阻塞等待 等待t2释放locker2 ,而t2尝试针对locker1加锁 也会阻塞等待等待t1 释放locker1.

这就相当于两个互相暗恋的人 你喜欢我 我也喜欢你 谁都在等对方 但是没人主动 说出来 终究是会错过。

针对这个问题 我们可以不用使用嵌套锁 ,但是也可以规定获取锁的顺序 比如说 t1和t2
线程规定好 先对locker1 加锁 再对locker2加锁 。这样就不会出现死锁的情况了。

产生死锁的四个必要条件

1.互斥条件
每个资源不能同时被两个或更多个线程使用。
2、不可剥夺性
一旦一个线程获得了 资源,除非该线程自己释放 ,否则其他线程不难强行剥夺这些资源。
3、请求和保持条件
一个线程因请求资源而阻塞时,必须保持自己的资源不放。如果一个线程在请求资源之前就释放了已获得的资源,那么就不会发生死锁现象。
4循环等待条件
如果存在一个资源等待链 ,即P1正在等待P2释放的资源 ,P2正在等待P3释放的资源 ,以此类推,最后Pn又在等待P1释放的资源。

以上四个条件必须同时满足,才能产生死锁现象 在实际开发中我们应该合理设计代码 避免死锁的发生。

在上面这四种产生死锁的条件中 前面两个是线程的基本特征 ,我们无法干预 ,最好解决死锁的方法就是破除条件3或者条件4 条件3 要破解 就需要 不要写锁嵌套 ,那如果非要写成锁嵌套怎么办 ,那就是破解第四个条件 当代码中有多个线程获取多把锁的情况 ,我们就需要统一规定好加锁的顺序 ,这样就能有效的避开死锁的现象。

内存可见性 引起的线程安全问题

下面我们通过一个代码来展示这个线程安全问题:

import java.util.Scanner;public class Demo1 {private  static int count = 0;public static void main(String[] args) {Thread t1 = new Thread(() -> {while (count == 0) {;}System.out.println("t1 执行结束");});Thread t2 = new Thread(() -> {Scanner scanner = new Scanner(System.in);System.out.println("请输入一个整数 :");count = scanner.nextInt();});t1.start();t2.start();}
}

运行结果:
在这里插入图片描述
你会发现虽然修改了count的值 但是程序并没有结束 这就和我们预期的出现了差错 出现bug了 那是为什么呢?

在那个while循环里面 会执行 load和cmp这两个指令
**load 😗*从内存读取数据到寄存器
cmp:(比较,同时产生跳转) 条件成立,就继续执行程序 条件不成立,就会跳到另一个地址来执行

当前的循环旋转速度很快 短时间内会有大量的load 和 cmp 反复执行的效果 load执行消耗的时间 会比 cmp 多很多
这样JVM 就会因为load执行速度慢 而每次load的结果都是一样的 JVM 就会干脆 把上面的load操作给优化了 只\有第一次执行load才是真的在进行load 后续再执行到相对应的代码,就不再真正的load了,而是直接读取已经load过的寄存器的值了
当我们在while循环体里面加入一些IO操作 程序运行就要正确了
这又是为什么呢
因为如果循环体里面有IO操作 就会使循环体的旋转速度大幅度降低 ,因为IO操作比load操作要慢得多 所以JVM也就不会再去优化load操作 ,而IO操作是不会被优化的.
内存可见性问题说到底是由于编译器优化引起的,优化掉load操作之后 ,使得t2线程的修改没有被t1线程感知到.
JVM在什么时候优化 什么时候不优化 这也是不确定的
那我们该怎么解决该内存可见性问题呢?

volatile

我们会引入volatile关键字
这个关键字的作用就是告诉编译器不要触发上述优化 volatile关键字是专门针对内存可见性的场景来解决问题的.

关于线程安全问题还有一些内容 我们下篇内容讲解 本篇内容就到此结束了 谢谢大家的浏览 !!!

相关文章:

Java中的多线程和线程安全问题

线程 线程是操作系统进行调度的最小单位。一个进程至少包含一个主线程&#xff0c;而一个线程可以启动多个子线程。线程之间共享进程的资源&#xff0c;但也有自己的局部变量。多线程程序和普通程序的区别&#xff1a;每个线程都是一个独立的执行流&#xff1b;多个线程之间是…...

java Web会议信息管理系统 用eclipse定制开发mysql数据库BS模式java编程jdbc

一、源码特点 jsp 会议信息管理系统是一套完善的web设计系统&#xff0c;对理解JSP java SERLVET mvc编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,eclipse开发&#xff0c;数据库为Mysql5.0&am…...

lock4j学习记录

一种简单的&#xff0c;支持不同方案的高性能分布式锁 简介 lock4j是一个分布式锁组件&#xff0c;其提供了多种不同的支持以满足不同性能和环境的需求。 立志打造一个简单但富有内涵的分布式锁组件。 特性 简单易用&#xff0c;功能强大&#xff0c;扩展性强。支持redis…...

【C++庖丁解牛】自平衡二叉搜索树--AVL树

&#x1f341;你好&#xff0c;我是 RO-BERRY &#x1f4d7; 致力于C、C、数据结构、TCP/IP、数据库等等一系列知识 &#x1f384;感谢你的陪伴与支持 &#xff0c;故事既有了开头&#xff0c;就要画上一个完美的句号&#xff0c;让我们一起加油 目录 前言1 AVL树的概念2. AVL…...

ES5和ES6的深拷贝问题

深拷贝我们知道是引用值的一个问题&#xff0c;因为在拷贝的时候&#xff0c;拷贝的是在内存中同一个引用。所以当其中的一个应用值发生改变的时候&#xff0c;其他的同一个引用值也会发生变化。那么针对于这种情况&#xff0c;我们需要进行深度拷贝&#xff0c;这样就可以做到…...

阿里云发送短信配置

依赖 <dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId><version>4.2.1</version> </dependency> <dependency><groupId>org.apache.httpcomponents</groupId&g…...

axios封装,请求取消和重试,请求头公共参数传递

axios本身功能已经很强大了&#xff0c;封装也无需过度&#xff0c;只要能满足自己项目的需求即可。 常规axios封装&#xff0c;只需要设置&#xff1a; 实现请求拦截实现响应拦截常见错误信息处理请求头设置 import axios from axios;// 创建axios实例 const service axios…...

隐私计算实训营学习五:隐语PSI介绍及开发指南

文章目录 一、SPU 实现的PSI介绍1.1 PSI定义和种类1.1.1 PSI定义和种类1.1.2 隐语PSI功能分层 1.2 SPU 实现的PSI介绍1.2.1 半诚实模型1.2.2 PSI实现位置 二、SPU PSI调度架构三、Secretflow PSI开发指南四、隐语PSI后续计划 一、SPU 实现的PSI介绍 1.1 PSI定义和种类 1.1.1 …...

ES的RestClient相关操作

ES的RestClient相关操作 Elasticsearch使用Java操作。 本文仅介绍CURD索引库和文档&#xff01;&#xff01;&#xff01; Elasticsearch基础&#xff1a;https://blog.csdn.net/weixin_46533577/article/details/137207222 Elasticsearch Clients官网&#xff1a;https://ww…...

linux通用命令 ssh命令连接慢问题排查

系列文章目录 文章目录 系列文章目录一、 ssh 连接慢3.1 查找原因3.2 解决方案 一、 ssh 连接慢 最近的 koji 服务器 使用 ssh 连接很慢。 3.1 查找原因 可以通过 ssh -vvv 192.168.0.123 或 time ssh root192.168.0.123 exit 查找原因如下&#xff1a; SERVER的SSHD会去DN…...

7.卷积神经网络与计算机视觉

计算机视觉是一门研究如何使计算机识别图片的学科&#xff0c;也是深度学习的主要应用领域之一。 在众多深度模型中&#xff0c;卷积神经网络“独领风骚”&#xff0c;已经被称为计算机视觉的主要研究根据之一。 一、卷积神经网络的基本思想 卷积神经网络最初由 Yann LeCun&a…...

Linux|如何管理多个Git身份

摘要 关于如何管理不同项目和多个Git身份。 作为一名通用软件开发者&#xff0c;我经常发现自己在处理各种各样的项目&#xff0c;每个项目都有自己的要求和期望。这包括为个人、工作和客户项目管理不同的Git身份。以下是我组织Git仓库以简化这一过程的方法。 目录组织 我将我的…...

力扣---最长回文子串---二维动态规划

二维动态规划思路&#xff1a; 首先&#xff0c;刚做完这道题&#xff1a;力扣---最长有效括号---动态规划&#xff0c;栈-CSDN博客&#xff0c;所以会有一种冲动&#xff0c;设立g[i]&#xff0c;表示以第i位为结尾的最长回文子串长度&#xff0c;然后再遍历一遍取最大长度即可…...

(一)kafka实战——kafka源码编译启动

前言 本节内容是关于kafka消息中间键的源码编译&#xff0c;并通过idea工具实现kafka服务器的启动&#xff0c;使用的kafka源码版本是3.6.1&#xff0c;由于kafka源码是通过gradle编译的&#xff0c;以及服务器是通过scala语言实现&#xff0c;我们要预先安装好gradle编译工具…...

Spring Boot 使用 Redis

1&#xff0c;Spring 是如何集成Redis的&#xff1f; 首先我们要使用jar包 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><gro…...

火车头通过关键词采集文章的原理

随着互联网信息的爆炸式增长&#xff0c;网站管理员和内容创作者需要不断更新和发布新的文章&#xff0c;以吸引更多的用户和提升网站的排名。而火车头作为一款智能文章采集工具&#xff0c;在这一过程中发挥着重要作用。本文将探讨火车头如何通过关键词采集文章&#xff0c;以…...

Kafka 面试题及参考答案

目录 1. Kafka 的核心特性是什么? 2. Kafka 为什么能够实现高吞吐量? 3. Kafka 的消息丢失是...

【Qt 学习笔记】Day1 | Qt 背景介绍

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ Day1 | Qt 背景介绍 文章编号&#xff1a;Qt 学习笔记 / 01 文章目录…...

springboot3.2.4+Mybatis-plus在graalvm21环境下打包exe

springboot3.2.4Mybatis-plus在graalvm21环境下打包exe 前提条件为之前已经能直接打包springboot3.2.4项目了然后在此基础上接入Mybatis-plus&#xff0c;然后能够正常进行打包exe并且执行&#xff0c;参考之前的文章进行打包 核心配置如下 package com.example.demo.config…...

Kubernetes(K8S)学习(二):K8S常用组件

K8S常用组件 一、 Controllers1、ReplicationController(RC)2、ReplicaSet(RS)3、Deployment 二、Labels and Selectors三、Namespace&#xff08;命名空间&#xff09;1、简介2、测试2.1、创建namespace2.2、创建pod 四、Network1、集群内&#xff1a;同一个Pod中的容器通信2、…...

如何使用群晖WebDAV实现固定公网地址同步Zotero文献管理器

文章目录 前言1. Docker 部署 Trfɪk2. 本地访问traefik测试3. Linux 安装cpolar4. 配置Traefik公网访问地址5. 公网远程访问Traefik6. 固定Traefik公网地址 前言 Trfɪk 是一个云原生的新型的 HTTP 反向代理、负载均衡软件&#xff0c;能轻易的部署微服务。它支持多种后端 (D…...

【JavaSE】初识线程,线程与进程的区别

文章目录 ✍线程是什么&#xff1f;✍线程和进程的区别✍线程的创建1.继承 Thread 类2.实现Runnable接口3.匿名内部类4.匿名内部类创建 Runnable ⼦类对象5.lambda 表达式创建 Runnable ⼦类对象 ✍线程是什么&#xff1f; ⼀个线程就是⼀个 “执行流”. 每个线程之间都可以按…...

全国青少年软件编程(Python)等级考试三级考试真题2023年9月——持续更新.....

青少年软件编程&#xff08;Python&#xff09;等级考试试卷&#xff08;三级&#xff09; 分数&#xff1a;100 题数&#xff1a;38 一、单选题(共25题&#xff0c;共50分) 1.有一组数据存在列表中,things[“桌子”,“椅子”,“茶几”,“沙发”,“西瓜”,“苹果”,“草莓”,“…...

react-navigation:

我的仓库地址&#xff1a;https://gitee.com/ruanjianbianjing/bj-hybrid react-navigation&#xff1a; 学习文档&#xff1a;https://reactnavigation.org 安装核心包: npm install react-navigation/native 安装react-navigation/native本身依赖的相关包: react-nativ…...

nginx负载均衡模式

轮询 (Round Robin) 用法&#xff1a;这是Nginx默认的负载均衡策略。每个请求会按顺序分配给upstream中的后端服务器&#xff0c;即按照配置的服务器列表顺序依次分配。 upstream backend {server backend1.example.com;server backend2.example.com;server backend3.example.…...

手写简易操作系统(十七)--编写键盘驱动

前情提要 上一节我们实现了锁与信号量&#xff0c;这一节我们就可以实现键盘驱动了&#xff0c;访问键盘输入的数据也属于临界区资源&#xff0c;所以需要锁的存在。 一、键盘简介 之前的 ps/2 键盘使用的是中断驱动的&#xff0c;在当时&#xff0c;按下键盘就会触发中断&a…...

springboot中基于RestTemplate 类 实现调用第三方API接口【POST版本】

https://blog.csdn.net/Drug_/article/details/135111675 这一篇的升级版 还是先配置文件 package com.init.config;import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.impl.clie…...

编程器固件修改教程

首发csdn&#xff0c;转载请说明出处&#xff0c;保留一切权益。 关于编程器固件 所谓编程器固件是用编程器读取嵌入式设备的FLASH存储数据生成的文件&#xff0c;类似于直接用工具复制整个硬盘 编程器固件与普通固件的差异 编程器固件是用特定的结构(按顺序、大小)将一些文件系…...

Python从原Excel表中抽出数据存入同一文件的新的Sheet(附源码)

python读取excel数据。Python在从原Excel表中抽出数据并存储到同一文件的新的Sheet中的功能&#xff0c;充分展示了其在数据处理和自动化操作方面的强大能力。这一功能不仅简化了数据迁移的过程&#xff0c;还提高了数据处理的效率&#xff0c;为数据分析和管理工作带来了极大的…...

计算机网络实验六:路由信息协议RIP

目录 6 实验六:路由信息协议RIP 6.1 实验目的 6.2 实验步骤 6.2.1 构建网络拓扑、配置各网络设备 6.2.2 网络功能验证测试 6.3 实验总结 6 实验六:路由信息协议RIP 6.1 实验目的 (1)学习RIP协议的工作原理和特点 (2)学习如何选择最短路径路由。 (3)进一步掌握…...

一个空间两个wordpress/月嫂免费政府培训中心

栈&#xff1a;​ 只有一个端口进入&#xff0c;元素先进后出FILO。 ​ 而栈内存正是使用了这种结构管理内存&#xff0c;所以才叫栈内存。 基于这个&#xff0c;我们通过链表的形式来实现栈的一系列操作&#xff0c;如入栈、出栈、获取栈顶元素等。 //节点结构体定义 typed…...

网站开发java好还是php好/清远新闻最新

正常情况下&#xff0c;当登陆用户异常掉线并不会通知服务器&#xff0c;这时服务器一直以为用户在线&#xff0c;解决这种问题有以下两种方法&#xff1a; 一、轮训检测连接情况&#xff0c;需要Timer轮训检测&#xff0c;如下代码&#xff1a;procedure TMainForm.Timer1Time…...

网站联系我们模板/青岛做网站推广

java虚拟机默认的编码是unicode指的是内存中的编码是unicode&#xff0c;而进行io&#xff08;包括网络和硬盘&#xff09;传输时另外编码&#xff0c;通过 System.getProperty("file.encoding")查看&#xff0c;通常&#xff0c;默认为ansi&#xff0c;不过通过ecli…...

济南微网站开发/百度一下首页网页手机版

jsp的常用指令有哪些(编译指令/动作指令整理) JSP动作指令 JSP - JSP中的脚本、指令、动作和注释...

输入公司名字找不到公司网站/seo免费工具

iphonex 已经上线有一段时间了&#xff0c;作为业界刘海屏幕第一款机型&#xff0c;导致全屏不能正常的全屏显示了&#xff0c;&#xff0c;所以需要对 iphonx 适配&#xff0c;下面就详细说说如何适配 先看一张适配前后的图: iphonex 提供的 meta 头 <meta name"vi…...

字体设计转换器/seo上海优化

本次结对编程是为了完成四则运算生成器&#xff0c;&#xff0c;针对这次结对编程的题目 我们决定支持最多 10 个运算符的应用程序&#xff0c;支持括号的运算&#xff0c;可以判断题目的对错。 这次代码的难点在于不仅仅要保证题目不重复&#xff0c;还要完成多功能。 需求分…...