JavaEE 【知识改变命运】04 多线程(3)
文章目录
- 多线程带来的风险-线程安全
- 线程不安全的举例
- 分析产出线程安全的原因:
- 1.线程是抢占式的
- 2. 多线程修改同一个变量(程序的要求)
- 3. 原子性
- 4. 内存可见性
- 5. 指令重排序
- 总结线程安全问题产生的原因
- 解决线程安全问题
- 1. synchronized关键字的介绍(监视器锁 monitor lock)
- a.锁的概念
- b.synchronized的特性
- c.synchronized的用法
- d.针对上述问题,加synchronied解决问题,分析底层逻辑
- e.关于synchronized的总结
- f.不同锁对象的情况
- g.如何判断多个线程竞争的是不是同一把锁
- h.可重入锁
- I.锁对象
- Java 标准库中的线程安全类
- 不安全类
- 安全类
- volatile 关键字
- 解决线程安全的问题
- 内存可见性
- 实例
- CPU层面保证可见性
- Java层面(保证指令顺序,从而保证内存可见性)
- 总结
- wait() 和 notify()
- wait和notify的基础知识
- wait()方法
- notify()⽅法
- notifyAll()⽅法
- wait 和 sleep和join的对⽐(⾯试题)
- wait和notify的总结
多线程带来的风险-线程安全
线程不安全的举例
场景: 用两个线程对同一个变量分别自增5万次,预期结果和自增结果是一个累加和,10万次。
public static int count;public static void main(String[] args) {Counter counter = new Counter();Thread t1 =new Thread(()->{for(int i=0;i<5_0000;i++){counter.count();}});Thread t2 =new Thread(()->{for(int i=0;i<5_0000;i++){counter.count();}});t1.start();t2.start();try {t1.join();t2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Count: "+counter.count);}public void count(){{count+=1;}} }
执行结果:
程序运行得到的结果与预期的结果值不一样,而且是一个错误的结果,而且我们程序的逻辑是正确的,这个现象所表现的问题称为线程安全问题
分析产出线程安全的原因:
1.线程是抢占式的
线程是抢占执行的(执行顺序是随机的)
由于线程的执行顺序无法为人控制,抢占式执行是造成线程安全问题的主要罪魁祸首,而且我们解决不了,完全是CPU自己调度,而且和CPU的核数有关
2. 多线程修改同一个变量(程序的要求)
单个线程修改同一个变量不会产生线程安全问题
多个线程修改不同的变量不会产生线程安全问题
多个线程修改同一个变量,会产生线程安全问题
3. 原子性
- 什么是原⼦性
我们把⼀段代码想象成⼀个房间,每个线程就是要进⼊这个房间的⼈。如果没有任何机制保证,A进⼊房间之后,还没有出来;B 是不是也可以进⼊房间,打断 A 在房间⾥的隐私。这个就是不具备原⼦性那我们应该如何解决这个问题呢?是不是只要给房间加⼀把锁,A
进去就把⻔锁上,其他⼈是不是就进不来了。这样就保证了这段代码的原⼦性了。 有时也把这个现象叫做同步互斥,表⽰操作是互相排斥的。- ⼀条 java 语句不⼀定是原⼦的,也不⼀定只是⼀条指令 比如上面的:count++,对应的是多条CPU指令 1:从内存或者寄存器读取count值 LOAD 2:执行自增 ADD 3:把计算结果写回寄存器或者内存 STORE
- 不保证原⼦性会给多线程带来什么问题 如果⼀个线程正在对⼀个变量操作,中途其他线程插⼊进来了,如果这个操作被打断了,结果就可能是错误的。 这点也和线程的抢占式调度密切相关. 如果线程不是 “抢占” 的, 就算没有原⼦性, 也问题不⼤.
4. 内存可见性
- 什么是内存可见性 一个线程对共享变量进行了修改,其他线程能感知到变量修改后的值。
- Java内存模型(JMM) java虚拟机规范定义了Java内存模型 ⽬的是屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到⼀致的并发效果.
- 分析Java内存模型
1.工作内存和线程之间是一一对应的
2.java的共享变量都在主内存里面,java线程线程首先从主内存读取变量的值到自己的工作内存
3.每个线程都有自己的工作内存,且线程工作内存直接是相互隔离的
4.线程在工作内存修改完变量的值后,又从工作内存把变量的值刷回主内存里面。
5.在以上执行count++操作,由于两个线程在执行,每个线程都有自己的工作内存,且相互不可见,最终导致了线程安全问题。线程对共享变量的修改线程之间相互感知不到- 注意: 为什么整这么多内存? 实际并没有这么多 “内存”. 这只是 Java 规范中的⼀个术语, 是属于 “抽象” 的叫法,所谓的 “主内存” 才是真正硬件⻆度的 “内存”. ⽽所谓的 “⼯作内存”, 则是指 CPU 的寄存器和⾼速缓存 为啥要这么⿇烦的拷来拷去? 因为
CPU 访问⾃⾝寄存器的速度以及⾼速缓存的速度, 远远超过访问内存的速度(快了 3 - 4 个数量级,也就是⼏千倍,
上万倍).那为什么不全部用寄存器,原因很简单,太贵了。- 关于JMM内存模型的面试题:JMM规定
1.所以线程不直接修改主内存中的共享变量
2.如果修改共享变量,需要把这个变量从主内存复制到自己的工作内存中,修改完之和再刷回主内存
3.各个线程之间不能相互通信,做到了内存级别的线程隔离。
5. 指令重排序
1.什么是指令重排序 我们写的代码,在编译之后可能与代码对应的指令顺序不同,这个过程就是指令重排序(JVM层面可能重排序,CPU执行指令也可能重排序)
1.一段代码是这样的 a.代阳去教室取英语书 b.代阳去食堂吃饭 c.代阳去教室去数学书 在单线程情况下,JVM,CPU指令集会对其优化,执行顺序按a–c–b的方式执行,也是没有问题,可以少跑一次教室,这就叫指令重排序
编译器对于指令重排序的前提是 “保持逻辑不发⽣变化”. 这⼀点在单线程环境下⽐较容易判断, 但是在多线程环境下就没那么容易了,
多线程的代码执⾏复杂程度更⾼, 编译器很难在编译阶段对代码的执⾏效果进⾏预测, 因此激进的重排序很容易导致优化后的逻辑和之前不等价
总结线程安全问题产生的原因
- 线程是抢占式执行的
- CPU的调度问题,硬件层面,我们解决不了
- 多个线程修改同一个变量
- 在真实业务场景中,使用多线程就是为了提升效率,在并发编成中这个需求是满足的
- 原子性
- 指令是在CPU上执行,怎么才能让CPU在执行时实现原子性,这个可能可以解决
- 内存可见性
- java层面应该可以解决,进程之间可以进行通信,那那么在线程中应该也有这样的机制,让线程在内存中也可以彼此感知
- 指令重排序
- 对于代码来说谁的优先级高,我们可以通过某种方式告诉编译器,不要对我的代码进行重排序
- 总结以上1,2我们不能改变,但是3,4,5我们可以进行改变,只要满足3,4,5中的一条或者多条,线程安全问题就可以解决
解决线程安全问题
1. synchronized关键字的介绍(监视器锁 monitor lock)
a.锁的概念
比如线程A拿到了锁,别的线程如果要执行被锁住的代码,那就要等到线程A释放锁之后,如果A没有释放锁,那么别的线程只能阻塞等待,这个状态就是BLOCK
b.synchronized的特性
- 互斥:synchronized会引起互斥效果,某个线程执行到某个对象的synchronized时,其他线程如果也执行到同一个对象sychronized就会阻塞等待
- 保证了原子性(通过加锁实现)
- 保证了内存可见性(通过串行执行实现)
- 不保证有序性
c.synchronized的用法
修饰方法:
- 修饰非静态方法:默认锁对象是this(当前对象)
- 修饰静态方法:默认锁对象是本身类
修饰代码块:
可以充当锁对象的是实例对象(new出来的对象,类对象,this)
d.针对上述问题,加synchronied解决问题,分析底层逻辑
public synchronized void count(){ // 修饰代码块加锁synchronized(this){ // count+=1; // }synchronized(this){count+=1;}} }
如果修饰方法:其实把方法进行了串行化处理
如果修饰的是代码块:其实把修饰代码块的内容,进行了串行话处理。对于部分类似这种要修改共享变量的情况进行串行话,其他代码模块继续并行执行,这样就可以提高效率
画图分析:
注意的点: t1释放锁之后,也可能第二次还是t1先于t2拿到锁,因为线程是抢占式执行的,不一定是t2
由于线程在执行逻辑之前要拿到锁,当拿到锁时,上一个线程已经执行完所有的指令,并把修改的值刷新会主内存,所有当前线程永远读到的是上一个线程执行完后的值synchronized保证了原子性
因为当前线程永远拿到的是前一个线程修改后的值,所有这样也现象上实现了内存可见性,但是并没有真正对内存可见性做出技术上的处理。
synchronized没有保证有序性(不会禁止指令重排序)
e.关于synchronized的总结
- 被synchronized修身的代码块会编成串行执行
- synchronized可以修饰方法或者代码块
- 被修饰的代码并不是一次性在CPU执行完,而是中途可能会被CPU调度走,当所有指令执行完后才会释放锁
- 只给一个线程加锁,也会出现线程安全
f.不同锁对象的情况
- 同·一个引用调用(静态和非静态)两个方法(一个用synchronized修饰一个不用synchronized不修饰)只加一把锁
public static int count;public static void main(String[] args) {Counter_Demo1 counter = new Counter_Demo1();Thread t1 =new Thread(()->{for(int i=0;i<5_0000;i++){counter.count();}});Thread t2 =new Thread(()->{for(int i=0;i<5_0000;i++){counter.count1();}});t1.start();t2.start();try {t1.join();t2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Count: "+counter.count);}public void count(){//修饰非静态代码块synchronized(this){count+=1;}}public void count1(){{count+=1;}}
//修饰非静态方法
// public void synchronized count(){
// {
// count+=1;
// }
// }
// public void count1(){
// {
// count+=1;
// }
// }
修饰静态的方法和代码块
// public static void count(){
// //修饰代码块
// //静态方法里面不能用this
// synchronized(Counter_Demo1.class){
// count+=1;
// }
// }
// public static void count1(){
// {
// count+=1;
// }
// }
// public synchronized static void count(){
// //修饰代码块
// //静态方法里面不能用this
// {
// count+=1;
// }
// }
// public static void count1(){
// {
// count+=1;
// }
// }
执行结果
都不符合预期
- 两个引用调用同一个方法(锁对象是实例对象new)
public static int count;public static void main(String[] args) {Counter_Demo1 counter1 = new Counter_Demo1();Counter_Demo1 counter2 = new Counter_Demo1();Thread t1 =new Thread(()->{for(int i=0;i<5_0000;i++){counter1.count();}});Thread t2 =new Thread(()->{for(int i=0;i<5_0000;i++){counter2.count();}});t1.start();t2.start();try {t1.join();t2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Count: "+counter1.count);}Object object=new Object();public void count(){synchronized (object){count+=1;}}
执行结果不符合预期
用类对象来加锁
static Object object= new Object();public void count(){synchronized (object){count+=1;}}
执行结果符合预期
结论:
- 只要一个线程A获得锁,没有锁竞争
- 线程A和线程B共同抢一把锁,谁先拿到锁就先执行谁,另一个线程就要阻塞等待,等到持有锁的线程释放锁之后再竞争锁
- 线程A与线程B抢的不是同一把锁,它们之间没有竞争关系,分别去拿到自己的锁,不存在锁关系
g.如何判断多个线程竞争的是不是同一把锁
- 实例对象:new出来的对象,每个都是单独存在
- 类中的属性:类没有用static修饰的变量,每个实例对象都是不同的
- 类中的静态成员变量:用static修饰,属于类对象,全局唯一
- 类对象:.class文件加载jvm之后的对象,全局唯一
- 线程之间是否存在锁竞争,关键是看访问的是不是同一个锁对象,如果是则存在锁竞争,如果不是则不存在锁竞争
h.可重入锁
- 对同一个锁对象和同一个线程,如果可以重复加锁,称之为不互斥,称之为可重入。
- 对同一个锁对象和同一线程,如果不可以重复加锁,称之为互斥,就会形成死锁。
- 已经获取锁对象的线程,如果再多次进行加锁操作,不会产生互斥现象
I.锁对象
- 锁对象记录了获取锁的线程信息
- 任何对象都可以做锁对象
- java中每个对象都是由以下几个部分组成:
– 1.markword
– 2.类型指针
– 3.实例数据
– 4.对齐填充
– 5对象默认带线啊哦是16byte
Java 标准库中的线程安全类
不安全类
- Arraylist
- LinkedList
- HashMap
- TreeMap
- HashSet
- TreeSet
- StringBuilder
安全类
-Vector(不推荐)
-
HashTable(不推荐)
-
CocurrentHashMap
-
StringBuffer
-
String (虽然没有加锁,但是不涉及修饰仍然是线程安全的)
volatile 关键字
解决线程安全的问题
- 真正意义上解决了内存可见性
- 解决了指令重排序(禁止指令重排序)问题
- 没有解决原子性问题
内存可见性
- 我们都知道,实际工作时候,访问的数据都是工作内存里面的数据,这样是为了保证效率,但是这样有时候会产生安全问题。但是加上 volatile , 强制读写内存. 速度是慢了, 但是数据变的更准确了
- 代码在写⼊ volatile 修饰的变量的时候
– 改变线程⼯作内存中volatile变量副本的值
– 将改变后的副本的值从⼯作内存刷新到主内存 - 代码在读取volatile修改的变量时候
–从主内存中读取volatile变量的最新值到线程的⼯作内存中
–从⼯作内存中读取volatile变量的副本
实例
static class Counter {public volatile int flag = 0;}public static void main(String[] args) {Counter counter = new Counter();Thread t1 = new Thread(() -> {while (counter.flag == 0) {// do nothing}System.out.println("循环结束!");});Thread t2 = new Thread(() -> {Scanner scanner = new Scanner(System.in);System.out.println("输⼊⼀个整数:");counter.flag = scanner.nextInt();});t1.start();t2.start();}
// 执⾏效果
// 当⽤⼾输⼊⾮0值时, t1 线程循环不会结束. (这显然是⼀个 bug)
//static class Counter {
// public volatile int flag = 0;
//}
// 执⾏效果
// 当⽤⼾输⼊⾮0值时, t1 线程循环能够⽴即结束.
-
对于线程t1来说,只是比较flag这个变量的值,从来都没有修改过,所有认为,这个值永远也不会改变,从而也不会重新从主内存中读取值(cpu为了提升高运行效率这个值一般存在寄存器或者cpu的缓存中)
-
在多线程环境下,就会出现出现这个问题,一个线程修改了另一个线程无法感知到的变量
CPU层面保证可见性
MESI缓存 一致协议(可以理解是一种通知机制)
Java层面(保证指令顺序,从而保证内存可见性)
内存屏障:作用是保证指令执行的顺序,从而保证内存可见性
volatile写:
volatile读:
有序性:用volatile 修改过的变量,由于前后有内存屏障,保证了指令的执行顺序,也可以理解为告诉编译器,不要进行指令重排序。
总结
volatile不保证原子性
public static volatile int count;public synchronized static void main(String[] args) {Counter counter = new Counter();Thread t1 =new Thread(()->{for(int i=0;i<5_0000;i++){counter.count();}});Thread t2 =new Thread(()->{for(int i=0;i<5_0000;i++){counter.count();}});t1.start();t2.start();try {t1.join();t2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Count: "+counter.count);}public void count(){count+=1;}
volatile保证可见性:MESI缓存 一致协议(可以理解是一种通知机制)
volatile保证有序性:内存屏障:作用是保证指令执行的顺序,从而保证内存可见性
wait() 和 notify()
wait和notify的基础知识
- wait() 和notify(),notifyAll()是object方法
- wait()/wait(long timeout):让线程进入等待的线程
- notify()/notifyAll():唤醒在当前对象上等待的线程
wait()方法
- wait做的事情
– 使当前执行代码的线程进行等待(把线程放到等待队列中)
– 释放当前锁
– 满足一定条件被唤醒,尝试重新获得这个锁
– wait要搭配sychronized来使用,脱离sychronized使用wait会之间抛出异常 - wait 结束等待的条件:
– 其他线程调用 调用该对象的notify方法
– wait等待时间超时(wait ⽅法提供⼀个带有 timeout 参数的版本, 来指定等待时间)
– 其他线程调用该等待线程的interrupted方法,导致wait抛出InterruptedException 异常. - wait()方法代码使用
public static void main(String[] args) throws InterruptedException {Object object = new Object();System.out.println("等待中");synchronized (object) {object.wait(1000);}System.out.println("等待结束");}这样在执⾏到object.wait()之后就⼀直等待下去,那么程序肯定不能⼀直这么等待下去了。这个时候就
需要使⽤到了另外⼀个⽅法唤醒的⽅法notify()。
notify()⽅法
- notify ⽅法是唤醒等待的线程.
– 方法notify()也要在同步方法或者同步代码块中执行,该方法是用来通知哪些可能等待该对象的对象锁的其他线程,对其发出通知,并使它们重新获取该对象对象锁
– 如果由多个线程等待,则有线程调度器随机挑选出一个呈现wait状态的线程。(并没有 “先来后到”))
– 在notify()方法后,当前线程不会立马释放该对象锁,需要等到notify方法线程将程序执行完,也就是退出同步代码块之后才会释放锁对象。 - 使用notify()方法唤醒线程
static class WaitTask implements Runnable {private Object locker;public WaitTask(Object locker) {this.locker = locker;}@Overridepublic void run() {synchronized (locker){try {System.out.println("等待开始") ;locker.wait();System.out.println("等待结束");} catch (InterruptedException e) {e.printStackTrace();}}}}static class NotifyTask implements Runnable {private Object locker;public NotifyTask(Object locker) {this.locker = locker;}@Overridepublic void run() {synchronized (locker) {System.out.println("notify 开始");locker.notify();System.out.println("notify 结束");}}}public static void main(String[] args) throws InterruptedException {Object locker = new Object();Thread t1 = new Thread(new WaitTask(locker));Thread t2 = new Thread(new NotifyTask(locker));t1.start();Thread.sleep(1000);t2.start();}
notifyAll()⽅法
- notify⽅法只是唤醒某⼀个等待线程. 使⽤notifyAll⽅法可以⼀次唤醒所有的等待线程.
static class WaitTask implements Runnable {private Object locker;public WaitTask(Object locker) {this.locker = locker;}@Overridepublic void run() {synchronized (locker) {try {System.out.println("等待开始");locker.wait();System.out.println("等待结束");} catch (InterruptedException e) {e.printStackTrace();}}}}static class NotifyTask implements Runnable {private Object locker;public NotifyTask(Object locker) {this.locker = locker;}@Overridepublic void run() {synchronized (locker) {System.out.println("notify 开始");locker.notifyAll();System.out.println("notify 结束");}}}public static void main(String[] args) throws InterruptedException {Object locker = new Object();Thread t1 = new Thread(new WaitTask(locker));Thread t3 = new Thread(new WaitTask(locker));Thread t4 = new Thread(new WaitTask(locker));Thread t2 = new Thread(new NotifyTask(locker));t1.start();t3.start();t4.start();sleep(1000);t2.start();}}
注意: 虽然是同时唤醒 3 个线程, 但是这 3 个线程需要竞争锁. 所以并不是同时执⾏, ⽽仍然是有先有后的执⾏
wait 和 sleep和join的对⽐(⾯试题)
- wait需要搭配synchronized使用 sleep,join不需要
- wait是Object的方法,sleep是Thread的静态方法,join是类中的方法(实例方法)
- 一个是用于线程之间的通信的,两个是让线程阻塞一段时间
- 相同点:可以让线程放弃执行一段时间
wait和notify的总结
- join和wait是两个不同的操作
–join是Thread类中的方法
– wait和notify是Object类中的方法
– join状态,主线程要等待子线程的结果
– wait是等待另一个线程的资源 - wait和notify必须跟synchronized一起使用,并且使用同一个对象
– 否则会报错
– wait的线程进入阻塞状态,调用wait的线程会释放自己持有的锁(不再占有cpu资源) - notify()和notifyAll(),
– notify随机唤醒一个线程,notifyAll唤醒所有线程,唤醒后的线程需要重新去竞争锁,拿到锁之后wait位置的代码才会继续执行。 - 使用小结
– wait和notify必须搭配synchronized一起使用
– wait和notify使用的锁对象必须是同一个
– notify执行多少次都没有关系(及时没有wait)(类似老板把包子做好空喊了一声) - 举例:
– 现实举例:
– 指令举例
相关文章:

JavaEE 【知识改变命运】04 多线程(3)
文章目录 多线程带来的风险-线程安全线程不安全的举例分析产出线程安全的原因:1.线程是抢占式的2. 多线程修改同一个变量(程序的要求)3. 原子性4. 内存可见性5. 指令重排序 总结线程安全问题产生的原因解决线程安全问题1. synchronized关键字…...

gz中生成模型
生成模型 通过服务调用生成 还记得parameter_bridge 吗? 我们在生成桥接的时候调用了这个cpp文件。 一个 parameter_bridge 实例用于消息传递(传感器数据)。之前的例子 另一个 parameter_bridge 实例用于服务桥接(动态生成模型…...

前端(Axios和Promis)
Promise 语法 <script>// 创建promise对象// 此函数需要再传入两个参数,都是函数类型let pnew Promise((resolve,reject)>{if(3>2){resolve({name:"李思蕾",age:23,地址:"河南省"});}else{reject("error");}});console.log(p);p.th…...

AI Agent:重塑业务流程自动化的未来力量(2/30)
《AI Agent:重塑业务流程自动化的未来力量》 摘要:整体思路是先介绍 AI Agent 的基本情况,再深入阐述其实现业务流程自动化的方法和在不同领域的应用,接着分析其价值和面临的挑战,最后得出结论,为读者全面…...

前端页面导出word
html-docx-js bug: vite使用html-docx.js会报错,点击下载上方文件替换即可 正文 npm install html-docx-js -S npm install file-saver -S<template><div id"managerReport">word内容......</div> </template><script>&l…...

【考前预习】1.计算机网络概述
往期推荐 子网掩码、网络地址、广播地址、子网划分及计算-CSDN博客 一文搞懂大数据流式计算引擎Flink【万字详解,史上最全】-CSDN博客 浅学React和JSX-CSDN博客 浅谈云原生--微服务、CICD、Serverless、服务网格_云原生 serverless-CSDN博客 浅谈维度建模、数据分析…...

ubuntu20.04复现 Leg-KILO
这里写目录标题 opencv版本问题下载3.2.0源代码进入解压后的目录创建构建目录运行 CMake 配置 配置时指定一个独立的安装目录,例如 /opt/opencv-3.2:出错: 使用多线程编译错误1: stdlib.h: 没有那个文件或目录错误2:er…...

Ensembl数据库下载参考基因组(常见模式植物)bioinfomatics 工具37
拟南芥参考基因组_拟南芥数据库-CSDN博客 1 Ensembl数据库网址 http://plants.ensembl.org/index.html #官网 如拟南芥等 那么问题来了,基因组fa文件和gff文件在哪里? 2 参考案例 拟南芥基因组fa在这里 注释gff文件在这里...

简单介绍web开发和HTML CSS_web网站开发流程
一、Web 开发:探索互联网世界的基石 1.1 什么是 Web 开发 Web 开发,简单来说,就是构建能够通过浏览器访问的网站的过程。Web 代表着全球广域网,也就是我们熟知的万维网(www),它连接着世界各地的…...

Docker 中使用 PHP 通过 Canal 同步 Mysql 数据到 ElasticSearch
一、Mysql 的安装和配置 1.使用 docker 安装 mysql,并且映射端口和 root 账号的密码 # 获取镜像 docker pull mysql:8.0.40-debian# 查看镜像是否下载成功 docker images# 运行msyql镜像 docker run -d -p 3388:3306 --name super-mysql -e MYSQL_ROOT_PASSWORD12…...

数据结构之五:排序
void*类型的实现:排序(void*类型)-CSDN博客 一、插入排序 1、直接插入排序 思想:把待排序的数据逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。 单趟&#x…...

科研绘图系列:R语言绘制热图和散点图以及箱线图(pheatmap, scatterplot boxplot)
禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍加载R包数据下载图1图2图3系统信息参考介绍 R语言绘制热图和散点图以及箱线图(pheatmap, scatterplot & boxplot) 加载R包 library(magrittr) library(dplyr) library(ve…...

基于 webRTC Vue 的局域网 文件传输工具
文件传输工具,匿名加密,只需访问网页,即可连接到其他设备,基于 webRTC 和 Vue.js coturn TURN 服务器 docker pull coturn/coturn docker run -d --networkhost \-v $(pwd)/my.conf:/etc/coturn/turnserver.conf \coturn/coturn…...

LeetCode 718. 最长重复子数组 java题解
https://leetcode.cn/problems/maximum-length-of-repeated-subarray/description/ 动态规划 class Solution {public int findLength(int[] nums1, int[] nums2) {int len1nums1.length,len2nums2.length;int[][] dpnew int[len11][len21];dp[0][0]0;//没有意义,…...

算法知识-15-深搜
一、概念 深度优先搜索(Deep First Search, DFS)是一种用于遍历或搜索树或图的算法。这种策略沿着树的深度遍历树的节点,尽可能深地搜索树的分支。 二、关键步骤 选择起点:根据题目要求,选择一个或多个节点作为搜索…...

区块链dapp 开发详解(VUE3.0)
1、安装metamask 插件。 2、使用封装的工具包: wagmi . 3、 wagmi 操作手册地址:connect | Wagmi 4、注意事项: 因为最初是react 版本,所以在VUE版的官方文档有很多地方在 import 用的是 wagmi,需要改为 wagmi/vue 。 连接成功后打印的内容如下&…...

Plugin [id: ‘flutter‘] was not found in any of the following sources解决方法
文章目录 错误描述解决方法修正方案:继续使用 apply from修正后的 build.gradle说明警告的处理进一步验证 错误描述 Plugin [id: ‘flutter’] was not found in any of the following sources: Gradle Core Plugins (not a core plugin, please see https://docs…...

专升本-高数 1
第 0 章,基础知识 一,重要公式 1、完全平方 (ab)a2abb (a-b)a-2abb 2、平方差公式 (a-b)(ab)a-b 3、立方差公式 a-b(a-b)(aabb) 4、 立方和公式 ab(ab)(a-abb) 二,基本初等函数 1,幂函数 一元二…...

【考前预习】3.计算机网络—数据链路层
往期推荐 【考前预习】2.计算机网络—物理层-CSDN博客 【考前预习】1.计算机网络概述-CSDN博客 浅谈云原生--微服务、CICD、Serverless、服务网格_云原生cicd-CSDN博客 子网掩码、网络地址、广播地址、子网划分及计算_子网广播地址-CSDN博客 浅学React和JSX-CSDN博客 目录 1.数…...

DockeUI 弱口令登录漏洞+未授权信息泄露
0x01 产品描述: DockerUI是一款开源的、强大的、轻量级的Docker管理工具。DockerUI覆盖了 docker cli 命令行 95% 以上的命令功能,通过可视化的界面,即使是不熟悉docker命令的用户也可以非常方便的进行Docker和Docker Swarm集群进行管理和维护。0x02 漏洞描述: DockerUI中存…...

【电子元器件】电感基础知识
本文章是笔者整理的备忘笔记。希望在帮助自己温习避免遗忘的同时,也能帮助其他需要参考的朋友。如有谬误,欢迎大家进行指正。 一、 电感的基本工作原理 1. 电感的基本工作原理如下: (1) 当线圈中有电流通过时&#…...

【SSH+X11】VsCode使用Remote-SSH在远程服务器的docker中打开Rviz
🚀今天来分享一下通过VsCode的Remote-SSH插件在远程服务器的docker中打开Rviz进行可视化的方法。 具体流程如下图所示,在操作开始前,请先重启设备,排除之前运行配置的影响: ⭐️ 我这里是使用主机连接服务器ÿ…...

Vue Web开发(五)
1. axios axios官方文档 异步库axios和mockjs模拟后端数据,axios是一个基于promise的HTTP库,使用npm i axios。在main.js中引入,需要绑定在Vue的prototype属性上,并重命名。 (1)main.js文件引用 imp…...

HarmonyOS:使用Grid构建网格
一、概述 网格布局是由“行”和“列”分割的单元格所组成,通过指定“项目”所在的单元格做出各种各样的布局。网格布局具有较强的页面均分能力,子组件占比控制能力,是一种重要自适应布局,其使用场景有九宫格图片展示、日历、计算器…...

开源Java快速自测工具,可以调用系统内任意一个方法
java快速测试框架,可以调到系统内任意一个方法,告别写单测和controller的困扰。 开源地址:https://gitee.com/missyouch/Easy-JTest 我们在开发时很多时候想要测试下自己的代码,特别是service层或者是更底层的代码,就…...

力扣刷题TOP101: 29.BM36 判断是不是平衡二叉树
目录: 目的 思路 复杂度 记忆秘诀 python代码 目的: 输入一棵节点数为 n 二叉树,判断该二叉树是否是平衡二叉树。 思路 什么是平衡二叉树(AVL 树)? 每个节点的左子树和右子树的高度差不能超过 1。确保…...

【在Linux世界中追寻伟大的One Piece】自旋锁
目录 1 -> 概述 2 -> 原理 3 -> 优缺点及使用场景 3.1 -> 优点 3.2 -> 缺点 3.3 -> 使用场景 4 -> 纯软件自旋锁类似的原理实现 4.1 -> 结论 5 -> 样例代码 1 -> 概述 自旋锁是一种多线程同步机制,用于保护共享资源避免受并…...

前端编辑器JSON HTML等,vue2-ace-editor,vue3-ace-editor
与框架无关 vue2-ace-editor有问题,ace拿不到(brace) 一些组件都是基于ace-builds或者brace包装的 不如直接用下面的,不如直接使用下面的 <template><div ref"editor" class"json-editor"><…...

C++ 中的运算符重载
运算符重载是C中的一种特性,它允许开发者为自定义类型定义或改变标准运算符的行为。通过运算符重载,你可以使得用户定义的类像内置类型一样使用运算符,比如加法、减法、赋值等。 如何在C中进行运算符重载? 重载运算符的语法&#…...

渗透测试工具 -- SQLmap安装教程及使用
随着网络安全问题日益严峻,渗透测试成为了保护信息安全的重要手段。而在渗透测试的众多工具中,SQLmap凭借其强大的自动化SQL注入检测和利用能力,成为了网络安全专家必备的利器。那么,你知道如何高效地使用SQLmap进行漏洞扫描吗&am…...