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

JavaEE多线程(2)

在这里插入图片描述

文章目录

  • 1..多线程的安全
    • 1.1出现多线程不安全的原因
    • 1.2解决多线程不安全的⽅法
    • 1.3三种典型死锁场景
    • 1.4如何避免死锁问题
    • 2.线程等待通知机制
    • 2.1等待通知的作用
    • 2.2等待通知的方法——wait
    • 2.3唤醒wait的方法——notify

1…多线程的安全

1.1出现多线程不安全的原因

  1. 线程在系统中是随机调度,抢占式执⾏。
  2. 多个线程同时修改同⼀个变量
  3. 线程对变量的修改操作不是“原⼦”的
  4. 内存可⻅性问题
  5. 指令重排序、
    “原⼦”是什么?
    原⼦是指不可再拆分的最⼩单位,放到代码操作中就是⼀段代码对应⼀个cpu指令就是原⼦的,如果对应到多个cpu指令就不是原⼦的。

1.2解决多线程不安全的⽅法

从原因⼊⼿:

  1. 原因1由于是系统本⾝的原因⼈为⽆法⼲预。
  2. 原因2是⼀个解决多线程不安全的⽅法,但是只是在特定的场景下才可以实现。
  3. 原因3是解决多线程不安全最合适的⽅法,既然修改的这个操作不是⼀个原⼦,那我们只需要将这
    个不是“原⼦”的操作打包成⼀个原⼦的操作即可。
    引申出⼀个新的词“锁”
    我们可以通过锁来将之前不是原⼦操作打包成⼀个原⼦操作
    关于这个锁本⾝就是系统内核中的api,只不过是jvm将这个api封装了,为了让java可以更好的使⽤这
    个锁。
    关于锁主要操作的两个⽅⾯:
    1.加锁
    ⽐如对t1线程进⾏加锁,t2线程也要加锁,那么t2线程就会阻塞等待(互斥锁/竞争锁/锁冲突)
    2.解锁
    t1线程解锁之后,t2线程才可以进⾏加锁
    注意:此处的锁主要针对于锁对象,对于t1和t2线程是指的同⼀个锁对象,不是同⼀个锁对象那就没
    意义了。
    对锁的总结:
    1.两个操作⸺加锁,解锁
    2.锁的特性⸺互斥
    3.只有多个线程竞争同⼀把锁才会互斥,如果多个线程竞争不同的锁则不会产⽣互斥
    java中⽤⼀个关键字来描述锁⸺synchronized
    1.synchronized 怎么读?怎么拼写?
    synchronized
    2.synchronized()括号中写的是锁对象⸺锁对象可以是任意类实列出的对象
    注意:
    锁对象的⽤途只有⼀个,两个线程是否针对⼀个对象加锁
    如果是就会出现锁竞争/锁冲突/互斥,就会引起阻塞等待
    如果不是就不会出现锁竞争/锁冲突/互斥,就不会引起阻塞等待
    和对象具体是什么类型,和它内部有什么属性,有什么⽅法,接下来是否要操作这个对象,统统都没
    有关系
    3.synchronized下⾯跟着{}
    当进⼊到这个代码块就是对上述(锁对象)进⾏了加锁操作
    当出了代码块就是对上述(锁对象)进⾏了解锁操作
    以下代码就是两个线程针对同⼀个对象locker加锁,当t1线程加锁之后,t2想加锁只能阻塞等待,只有
    等到t1线程解锁之后,t2线程才有可能加锁成功
public static int count = 0;
//创建一个对象作为一个锁对象
public static void main(String[] args) throws InterruptedException {Object locker = new Object();Thread t1 = new Thread(()-> {for (int i = 1; i <= 5000; i++) {synchronized(locker) {count++;}}});Thread t2 = new Thread(()-> {for (int i = 1; i <=5000 ; i++) {synchronized(locker) {count++;}}});t1.start();t2.start();t1.join();t2.join();System.out.println("count = "+ count);
}

以下代码就是两个线程对不同锁对象进行加锁,就不会产生锁冲突/锁竞争/互斥。

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

当t1线程解锁之后,不一定是t2线程拿到锁,有可能是其他线程拿到锁。
join与锁的区别:
join是一个线程完了,再让第二个线程执行。
锁只是针对加锁的那一块代码,就像上述代码中加锁的count++就会变成串行执行,但剩余的代码还是并发执行。
注意:加锁不是针对线程,而是针对共享资源的访问操作,比如现在我对t1线程中的操作1进行了加锁,但是系统内核将t1线程调度走了,可以让其他线程调度到t1线程的位置继续执行操作1,此时t2线程还是无法加到锁.
另外加锁的方式:
1.写一个方法将加锁的关键字放在方法中:

public static int count = 0;
synchronized public void add() {count++;
}
public static void main(String[] args) throws InterruptedException {Deom14 deom14 = new Deom14();Thread t1 = new Thread(()-> {for (int i = 1; i <= 5000; i++) {deom14.add();}});Thread t2 = new Thread(()-> {for (int i = 1; i <=5000 ; i++) {deom14.add();}});t1.start();t2.start();t1.join();t2.join();System.out.println("count = "+ count);
}

对于这种方式加锁还有一种写法:

public static int count = 0;public void add() {synchronized (this){count++;}
}
public static void main(String[] args) throws InterruptedException {Deom14 deom14 = new Deom14();Thread t1 = new Thread(()-> {for (int i = 1; i <= 5000; i++) {deom14.add();}});Thread t2 = new Thread(()-> {for (int i = 1; i <=5000 ; i++) {deom14.add();}});t1.start();t2.start();t1.join();t2.join();System.out.println("count = "+ count);
}

利用this,谁调用了这个方法就用这个对象作为锁对象,
2.synchronized对static方法进行加锁,相当于对类的类对象进行加锁

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

对于这种写法有个致命的缺点,一旦有多个线程调用func,则这些线程都会触发锁竞争。
4. 原因4引起的线程不安全——编译器优化产生的线程不安全

public static int count;
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();
}

在t1线程中while(count == 0)这个操作在指令的角度来分析就两个指令分别为load和cmp指令
load指令将内存中数据读入cpu中寄存器
cmp在cpu寄存器中进行比较
1.内存读取数据的速度远远小于寄存器读取的数据的速度就会造成load指令执行的速度远远慢于cmp指令执行。
2.在t2线程未修改count之前load指令执行的结果是一样的。
由上述两个原因,java编译器为了提高效率就会将load指令这个操作优化,所以当t2线程修改了count的值t1线程也不会感知到。
如果在t1线程的循环体中加一些I/O操作或者阻塞操作,这样java编译器就不会去优化load指令。

public static int count;
public static void main(String[] args) {Thread t1 = new Thread(() -> {while(count == 0) {try {Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("退出t1线程");});Thread t2 = new Thread(() -> {Scanner scanner = new Scanner(System.in);System.out.println("输入一个数:");count = scanner.nextInt();});t1.start();t2.start();
}

如何解决由编译器优化引起的线程不安全?
1.上述在循环体中加I/O操作或者阻塞操作可以解决
2.用volatile关键字来修饰需要修改的变量——这个关键字只能解决编译器优化带来的内存可见性问题,不能解决原因三带来的问题。
volatile关键字
当为count加上volatile关键字就会告知编译器这个变量不能随便被优化`在这里插入代码片

public volatile static int count;
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();
}
  1. 原因5引起的线程不安全——编译器优化策略导致
    指令重排序旨在编译器在优化的时候将你的代码重新调整执行顺序来提高效率,优化前的逻辑与优化后的逻辑是等价的,在单线程中指令重排序这个优化策略是不会造成线程不安全,但是在多线程中就会导致线程不安全。
    面对指令重排序我们采取用volatile关键字
class SinlgetonLazy1 {private static volatile SinlgetonLazy1 instance = null;public static SinlgetonLazy1 getInstance() {Object locker = new Object();if(instance == null) {synchronized(locker) {if (instance == null ) {instance = new SinlgetonLazy1();}}}return instance;}private SinlgetonLazy1() {}
}
public class Deom24 {public static void main(String[] args) throws InterruptedException {SinlgetonLazy1 s = SinlgetonLazy1.getInstance();Thread t1 = new Thread(() -> {SinlgetonLazy1 s1 = SinlgetonLazy1.getInstance();});Thread t2 = new Thread(() -> {SinlgetonLazy1 s2 = SinlgetonLazy1.getInstance();});t1.start();t2.start();}
}

1.3三种典型死锁场景

场景一:锁是不可重入锁,并且一个线程针对一个锁对象,连续被加锁两次。
采取可重入锁(synchronized)就可以对这个问题迎刃而解了
场景二:两个线程两把锁
现有t1线程t2线程和locker1锁locker2锁, locker2锁要对t1线程中内容加锁,但同时locker2锁对t2线程还未解锁,所以t1线程需要阻塞等待,而现在locker1锁也要对t2线程中的内容加锁,但是locker1对t1线程还未解锁,所以t2线程需要阻塞等待,这样就导致你等我,我等你的死锁现象`

public static void main(String[] args) {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();
}

上述代码就是场景二死锁的实现代码
通过jconsole窗口观察:
在这里插入图片描述
此时就是t1线程想要获取到locker2锁,但locker2锁被t2线程给加锁了并且没有解锁,所以此时的t1线程想要获取locker2锁只能阻塞等待。
在这里插入图片描述
此时就是t2线程想要获取到locker1锁,但locker1锁被t1线程给加锁了并且没有解锁,所以此时的t2线程想要获取locker1锁只能阻塞等待。
场景三:N个线程M把锁——哲学家就餐问题
约定每一个哲学家必须先获取编号小的筷子,后获取编号大的筷子,就可以解决哲学家就餐问题。

1.4如何避免死锁问题

出现死锁的四大必要条件,少一个都不会出现死锁。

  1. 锁具有互斥特性(基本特点,一个线程拿到锁之后,其他线程就得阻塞等待)——基本特点
  2. 锁不可抢占(不可被剥夺)——基本特点
  3. 请求和保持(一个线程拿到一把锁之后,不释放这个锁的前提下,再尝试获取其他锁)——代码结构
  4. 循环等待(多个线程获取多个锁的过程中,出现了循环等待,A线程等待B线程,B线程又等待A线程)——代码结构
    要避免死锁,由于第一和第二点是锁的基本特性所以我们无法避免,我们只能从第三点和第四点出发避免死锁。
    针对第三点:我们尽量不要出现嵌套锁。
    针对第四点:我们可以约定加锁的顺序,让所有的线程按照加锁的顺序来获取锁。
    针对上述的代码出现了死锁,我们就可以约定加锁的先后顺序来避免死锁,我们约定locker1先加锁,locker2后加锁。
public static void main(String[] args) {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 (locker1) {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker2) {System.out.println("t2线程获取到了两把锁");}}});t1.start();t2.start();
}

2.线程等待通知机制

2.1等待通知的作用

通过条件,判断当前逻辑是否能够执行,如果不满足条件不能执行,那么就主动进行阻塞(wait)让其他线程来调度cpu的资源,等到条件满足的时候,再让其它线程(阻塞的线程)来唤醒。

2.2等待通知的方法——wait

  1. wait方法是Object类提供,所以任何对象都能调用这个方法
  2. wait方法和sleep一样会被interrupt打断并且自动清空标志位
  3. wait方法不仅仅可一个阻塞等待还可以解锁
  4. wait方法要放在synchronized内部使用
public static void main(String[] args) {Object locker = new Object();Thread t1 = new Thread(() -> {synchronized (locker) {try {locker.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}});

在这里插入图片描述

2.3唤醒wait的方法——notify

  1. notify方法要放在synchronized内部使用
  2. notify方法是Object类提供,所以任何对象都能调用这个方法wait方法是Object类提供,所以任何对象都能调用这个方法
  3. notify是随机唤醒被阻塞的线程
  4. notifyAll()方法可以唤醒所有被阻塞的线程,但这种方法不常用。`
public static void main(String[] args) {Object locker = new Object();Thread t1 = new Thread(() -> {System.out.println("t1线程等待之前");synchronized (locker) {try {locker.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("t1线程等待结束");});Thread t2 = new Thread(() -> {System.out.println("t2线程唤醒t1线程之前");synchronized (locker) {try {Thread.sleep(3000);locker.notify();} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("t2线程唤醒结束");});t1.start();t2.start();
}

此时的t1线程的状态转化为:WAITTING——RUNNABLE——BLOCKED

相关文章:

JavaEE多线程(2)

文章目录 1..多线程的安全1.1出现多线程不安全的原因1.2解决多线程不安全的⽅法1.3三种典型死锁场景1.4如何避免死锁问题2.线程等待通知机制2.1等待通知的作用2.2等待通知的方法——wait2.3唤醒wait的方法——notify 1…多线程的安全 1.1出现多线程不安全的原因 线程在系统中…...

中新赛克两款数据安全产品成功获得“可信数安”评估测试证书

6月19日&#xff0c;2024数据智能大会在北京盛大召开。 会上&#xff0c;中国2024年上半年度“可信数安”评估测试证书正式颁发。中新赛克两款参评产品凭借过硬的技术水准和卓越的应用效果&#xff0c;成功获得专项测试证书。 2024年上半年度“可信数安”评估测试通过名单 中新…...

代码随想录——分割回文串(Leetcode 131)

题目链接 回溯 class Solution {List<List<String>> res new ArrayList<List<String>>();List<String> list new ArrayList<String>();public List<List<String>> partition(String s) {backtracking(s, 0);return res;}p…...

Rust 学习方法及学习路线汇总

Rust 学习方法及学习路线汇总 Rust 是一种系统编程语言&#xff0c;旨在提供安全性、并发性和高性能。它是由 Mozilla 公司开发的&#xff0c;于 2010 年首次发布。Rust 能够帮助开发者编写可靠和高效的软件&#xff0c;因此受到了广泛的关注和认可。 如果你有兴趣学习 Rust&…...

一名女DBA的感谢信,到底发生了什么?

昨日我们收到这样一通来电 “早上九点刚上班便收到业务投诉电话&#xff0c;系统卡顿&#xff0c;接口失败率大增&#xff0c;怀疑数据库问题。打开运维平台发现是国产库&#xff0c;生无可恋&#xff0c;第一次生产环境遇到国产库性能问题&#xff0c;没什么排查经验&#xf…...

群晖NAS本地部署并运行一个基于大语言模型Llama2的个人本地聊天机器人

前言 本文主要分享如何在群晖 NAS 本地部署并运行一个基于大语言模型 Llama 2 的个人本地聊天机器人并结合内网穿透工具发布到公网远程访问。本地部署对设备配置要求高一些,如果想要拥有比较好的体验,可以使用高配置的服务器设备. 目前大部分大语言模型的产品都是基于网络线上…...

HarmonyOS模拟器(phone-x86-api9)一直卡顿的解决方法

在DevEco Studio 3.1.1 Release版本中的Device Manager中创建本地的模拟器&#xff0c;创建phone-x86-api9模拟器成功&#xff0c;但是启动该新建的模拟器一直显示"HarmonyOS"logo图片&#xff0c;然后一直卡在这里&#xff0c;运行结果如下所示&#xff1a; 检查模…...

排序题目:有序数组的平方

文章目录 题目标题和出处难度题目描述要求示例数据范围进阶 解法一思路和算法代码复杂度分析 解法二思路和算法代码复杂度分析 题目 标题和出处 标题&#xff1a;有序数组的平方 出处&#xff1a;977. 有序数组的平方 难度 2 级 题目描述 要求 给定按非递减顺序排序的整…...

PPT可以转换成Word吗?归纳了三种转换方式

PPT可以转换成Word吗&#xff1f;在当今快节奏的工作和学习环境中&#xff0c;不同格式文件之间的转换变得日益重要。PPT作为演示文稿制作的首选工具&#xff0c;广泛应用于会议演讲、教育培训等多个场景&#xff0c;而Word则是文档编辑与编排的基石。为了便于进一步编辑、分享…...

分布式锁三种方案

基于数据库的分布式锁&#xff08;基于主键id和唯一索引&#xff09; 1基于主键实现分布式锁 2基于唯一索引实现分布式锁 其实原理一致&#xff0c;都是采用一个唯一的标识进行判断是否加锁。 原理&#xff1a;通过主键或者唯一索性两者都是唯一的特性&#xff0c;如果多个…...

【HarmonyOS NEXT】har 包的构建生成过程

Har模块文件结构 构建HAR 打包规则 开源HAR除了默认不需要打包的文件&#xff08;build、node_modules、oh_modules、.cxx、.previewer、.hvigor、.gitignore、.ohpmignore&#xff09;和.gitignore/.ohpmignore中配置的文件&#xff0c;cpp工程的CMakeLists.txt&#xff0c;…...

从0开发一个Chrome插件:项目实战——翻译插件(附带申请谷歌翻译、百度翻译教程)

前言 这是《从0开发一个Chrome插件》系列的第十八篇文章,本系列教你如何从0去开发一个Chrome插件,每篇文章都会好好打磨,写清楚我在开发过程遇到的问题,还有开发经验和技巧。 专栏: 从0开发一个Chrome插件:什么是Chrome插件?从0开发一个Chrome插件:开发Chrome插件的必…...

查看nginx安装/配置路径,一个服务器启动两个nginx

查看nginx安装/配置路径 查看nginx的pid&#xff1a; ps -ef | grep nginx查看pid对应服务的启动路径 ll /proc/2320/exe使用检查配置文件命令&#xff0c;查看配置文件位置 /usr/local/nginx/sbin/nginx -t一个服务启动两个nginx 拷贝一份程序&#xff0c;cpbin是我自己创…...

JavaScript中 Map与reduce的应用

1. Map&#xff1a;映射新世界 Map构造函数创建一个新Map对象&#xff0c;它允许你以键值对的形式存储数据&#xff0c;提供了一种更加灵活的数据结构。与传统的对象相比&#xff0c;Map允许任何值&#xff08;包括对象&#xff09;作为键&#xff0c;而且具有更好的性能表现。…...

1688商品详情API:一键解锁海量批发数据

引言 1688作为阿里巴巴旗下的B2B交易平台&#xff0c;拥有庞大的商品数据库和丰富的供应商资源。对于想要获取商品详细信息的开发者和企业而言&#xff0c;1688提供的API接口是获取一手数据的关键途径。本文将详细介绍如何使用1688商品详情API&#xff0c;包括注册、获取API密…...

C#结合JS 修改解决 KindEditor 弹出层问题

目录 问题现象 原因分析 范例运行环境 解决问题 修改 kindeditor.js C# 服务端更新 小结 问题现象 KindEditor 是一款出色的富文本HTML在线编辑器&#xff0c;关于编辑器的详细介绍可参考我的文章《C# 将 TextBox 绑定为 KindEditor 富文本》&#xff0c;这里我们讲述在…...

二开的精美UI站长源码分享论坛网站源码 可切换皮肤界面

二开的精美UI站长源码分享论坛网站源码 可切换皮肤界面 二开的精美UI站长源码分享论坛网站源码 可切换皮肤界面...

【diffusers极速入门(三)】生成的图像尺寸与 UNet 和 VAE 之间的关系

先上结论&#xff0c;一句话总结即&#xff1a; SD 图片的输入\输出尺寸&#xff08;高或宽&#xff09; Unet 输入\输出的样本尺寸&#xff08;高或宽&#xff09; x VAE 的缩放尺寸 在使用生成模型时&#xff0c;特别是图像生成任务中&#xff0c;理解 UNet 和 VAE&#xf…...

react实现窗口悬浮框,可拖拽、折叠、滚动

1、效果如下 2、如下两个文件不需要修改 drag.js import React from "react"; import PropTypes from "prop-types";export default class DragM extends React.Component {static propTypes {children: PropTypes.element.isRequired};static defaultP…...

52【场景作图】空间感

参考 场景绘制&#xff0c;画面空间感如何拉开&#xff1f;分分钟就能学会的场景优化思路更新啦&#xff01;_哔哩哔哩_bilibili https://www.bilibili.com/video/BV1pa411J7Ps/?spm_id_from333.337.search-card.all.click&vd_source20db0c4e2d303527ed13c4b9cdf698ec 1 …...

SpringBoot系列之搭建WebSocket应用

SpringBoot系列之ServerEndpoint方式开发WebSocket应用。在实时的数据推送方面&#xff0c;经常会使用WebSocket或者MQTT来实现&#xff0c;WebSocket是一种不错的方案&#xff0c;只需要建立连接&#xff0c;服务端和客户端就可以进行双向的数据通信。很多网站的客户聊天&…...

RK3568技术笔记十四 Ubuntu创建共享文件夹

单击“虚拟机”&#xff0c;单击“设置”&#xff0c;如图所示&#xff1a; 单击“选项”&#xff0c;选择“总是启用&#xff08;E&#xff09;”&#xff0c;单击“添加”&#xff0c;如图所示&#xff1a; 单击“下一步”&#xff0c;如图所示&#xff1a; 单击“浏览”添加…...

JavaScript 获取地理位置 Geolocation

在现代的 web 应用程序中&#xff0c;获取用户的地理位置信息是一项常见的需求。这可以用于提供个性化内容、本地化服务或者基于位置的功能。HTML5 引入了 Geolocation API&#xff0c;使得从浏览器中获取地理位置信息变得非常简单。 1. Geolocation API 简介 Geolocation AP…...

android串口助手apk下载 源码 演示 支持android 4-14及以上

android串口助手apk下载 1、自动获取串口列表 2、打开串口就开始接收 3、收发 字符或16进制 4、默认发送at\r\n 5、android串口助手apk 支持android 4-14 &#xff08;Google seral port 太老&#xff09; 源码找我 需要 用adb root 再setenforce 0进入SELinux 模式 才有权限…...

windows11 生产力工具配置

一、系统安装 官方windows11.iso镜像文件安装操作系统时&#xff0c;会强制要求联网验证&#xff0c;否则无法继续安装操作系统&#xff0c;跳过联网登录账号的方式为&#xff1a;按下【shiftF10】快捷键&#xff0c;调出cmd命令窗口&#xff0c;输入命令 OOBE\BYPASSNRO 等…...

Nacos配置中心不可用会有什么影响

服务端&#xff1a; Nacos的数据存储接口 com.alibaba.nacos.config.server.service.DataSourceService 有两种实现&#xff1a; 如果指定了mysq 作为数据库&#xff0c;则必须使用 mysql 如果是 集群方式部署Nacos&#xff0c;则必须使用mysql 如果是单例方式部署 并且 没…...

AI时代下的自动化代码审计工具

代码审计工具分享 吉祥学安全知识星球&#x1f517;除了包含技术干货&#xff1a;Java代码审计、web安全、应急响应等&#xff0c;还包含了安全中常见的售前护网案例、售前方案、ppt等&#xff0c;同时也有面向学生的网络安全面试、护网面试等。 这两年一直都在提“安全左移”&…...

不懂索引,简历上都不敢写自己熟悉SQL优化

大家好&#xff0c;我是考哥。 今天给大家带来MySQL索引相关核心知识。对MySQL索引的理解甚至比你掌握SQL优化还重要&#xff0c;索引是优化SQL的前提和基础&#xff0c;我们一步步来先打好地基。 当MySQL表数据量不大时&#xff0c;缺少索引对查询性能的影响不会太大&#x…...

C# 设置PDF表单不可编辑、或提取PDF表单数据

PDF表单是PDF中的可编辑区域&#xff0c;允许用户填写指定信息。当表单填写完成后&#xff0c;有时候我们可能需要将其设置为不可编辑&#xff0c;以保护表单内容的完整性和可靠性。或者需要从PDF表单中提取数据以便后续处理或分析。 之前文章详细介绍过如何使用免费Spire.PDF…...

面试篇-求两个有序数组的交集

题目 两个有序数组&#xff0c;第一个有序数组m是1000w个元素&#xff0c;第二个有序数组n是1000个元素&#xff0c;求交集&#xff0c;需要考虑时间复杂度和空间复杂度。 解题思路 解法1&#xff1a;遍历小数组n&#xff0c;在m数组中进行折半查找&#xff0c;根据数组有序…...

wordpress怎么加地图吗/推广平台

阿里云短信服务1、首先登录阿里云账号开通短信服务2、签名管理3、模板管理1、首先登录阿里云账号开通短信服务 进入阿里云主页面&#xff0c;搜索短信服务 进入短信服务页面&#xff0c;点击产品详情页 首次使用短信服务需要开题&#xff0c;点击免费开通 点击立即开通&#…...

网站购物功能如何做/黑河seo

本文标题是仿购物阅读类app的分类界面&#xff0c;因为大多数app的分类界面都是这样&#xff0c;尤购物类阅读类居多。本文效果是仿京东阅读分类界面。之前无意安装了京东阅读&#xff0c;无意中看到了一个分类界面&#xff0c;感觉效果很好&#xff0c;就想写下来。于是乎有了…...

java做网站没有php好吗/优化关键词首页排行榜

正排索引&#xff08;正向索引&#xff09; 正排表是以文档的ID为关键字&#xff0c;表中记录文档中每个字的位置信息&#xff0c;查找时扫描表中每个文档中字的信息直到找出所有包含查询关键字的文档。 正排表结构如图1所示&#xff0c;这种组织方法在建立索引的时候结构比较简…...

石做视频网站需要牌照/搜索引擎的关键词优化

失语者的狂欢&#xff0c;觉醒者的自焚 ——《杀生》影评 疯癫者是理性世界的迟到者。 ——题记 “境由心生&#xff0c;心由境生”如果说&#xff0c;前者是崇高者的自我救赎&#xff0c;后者&#xff0c;则是千千万万个无助灵魂的挣扎。而对于欲望的封锁&#xff0c;正是心生…...

做计算机版权需要网站源代码/百度投放广告平台

大数据平台架构 大数据技术已经被应用到各行各业&#xff0c;涉及人们生活的方方面面。大数据技术大大提高了数据存储和计算能力&#xff0c;从而为企业快速决策提供了数据支撑&#xff0c;能够助力企业改进业务流程、控制成本、提高产品质量&#xff0c;应用大数据技术为企业…...

公众号里链接的网站怎么做的/第一推广网

从年初开始&#xff0c;各大厂商陆续推出了自己的 VR 设备&#xff0c;亮点十足。 去年你也许还觉得虚拟现实是一个看不到未来的市场&#xff0c;但是从今年开始&#xff0c;一切都在向积极的方向发展。据德意志银行的观点&#xff0c;现在的虚拟现实正如 2007 年的智能手机。…...