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

JavaEE 【知识改变命运】03 多线程(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的方式执行,也是没有问题,可以少跑一次教室,这就叫指令重排序
编译器对于指令重排序的前提是 “保持逻辑不发⽣变化”. 这⼀点在单线程环境下⽐较容易判断, 但是在多线程环境下就没那么容易了,
多线程的代码执⾏复杂程度更⾼, 编译器很难在编译阶段对代码的执⾏效果进⾏预测, 因此激进的重排序很容易导致优化后的逻辑和之前不等价

总结线程安全问题产生的原因

  1. 线程是抢占式执行的
  • CPU的调度问题,硬件层面,我们解决不了
  1. 多个线程修改同一个变量
  • 在真实业务场景中,使用多线程就是为了提升效率,在并发编成中这个需求是满足的
  1. 原子性
  • 指令是在CPU上执行,怎么才能让CPU在执行时实现原子性,这个可能可以解决
  1. 内存可见性
  • java层面应该可以解决,进程之间可以进行通信,那那么在线程中应该也有这样的机制,让线程在内存中也可以彼此感知
  1. 指令重排序
  • 对于代码来说谁的优先级高,我们可以通过某种方式告诉编译器,不要对我的代码进行重排序
  1. 总结以上1,2我们不能改变,但是3,4,5我们可以进行改变,只要满足3,4,5中的一条或者多条,线程安全问题就可以解决

解决线程安全问题

1. synchronized关键字的介绍(监视器锁 monitor lock)

a.锁的概念

比如线程A拿到了锁,别的线程如果要执行被锁住的代码,那就要等到线程A释放锁之后,如果A没有释放锁,那么别的线程只能阻塞等待,这个状态就是BLOCK

b.synchronized的特性
  1. 互斥:synchronized会引起互斥效果,某个线程执行到某个对象的synchronized时,其他线程如果也执行到同一个对象sychronized就会阻塞等待
  2. 保证了原子性(通过加锁实现)
  3. 保证了内存可见性(通过串行执行实现)
  4. 不保证有序性
c.synchronized的用法

修饰方法:

  1. 修饰非静态方法:默认锁对象是this(当前对象)
  2. 修饰静态方法:默认锁对象是本身类
    修饰代码块:
    可以充当锁对象的是实例对象(new出来的对象,类对象,this)
d.针对上述问题,加synchronied解决问题,分析底层逻辑
    public synchronized void count(){ //  修饰代码块加锁synchronized(this){ //            count+=1; //        }synchronized(this){count+=1;}} }

在这里插入图片描述
如果修饰方法:其实把方法进行了串行化处理
如果修饰的是代码块:其实把修饰代码块的内容,进行了串行话处理。对于部分类似这种要修改共享变量的情况进行串行话,其他代码模块继续并行执行,这样就可以提高效率

画图分析:
在这里插入图片描述
注意的点: t1释放锁之后,也可能第二次还是t1先于t2拿到锁,因为线程是抢占式执行的,不一定是t2
由于线程在执行逻辑之前要拿到锁,当拿到锁时,上一个线程已经执行完所有的指令,并把修改的值刷新会主内存,所有当前线程永远读到的是上一个线程执行完后的值

synchronized保证了原子性
因为当前线程永远拿到的是前一个线程修改后的值,所有这样也现象上实现了内存可见性,但是并没有真正对内存可见性做出技术上的处理。
synchronized没有保证有序性(不会禁止指令重排序)

e.关于synchronized的总结
  1. 被synchronized修身的代码块会编成串行执行
  2. synchronized可以修饰方法或者代码块
  3. 被修饰的代码并不是一次性在CPU执行完,而是中途可能会被CPU调度走,当所有指令执行完后才会释放锁
  4. 只给一个线程加锁,也会出现线程安全
f.不同锁对象的情况
  1. 同·一个引用调用(静态和非静态)两个方法(一个用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;
//        }
//    }

执行结果
都不符合预期
在这里插入图片描述

  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;}}

执行结果符合预期
在这里插入图片描述
结论:

  1. 只要一个线程A获得锁,没有锁竞争
  2. 线程A和线程B共同抢一把锁,谁先拿到锁就先执行谁,另一个线程就要阻塞等待,等到持有锁的线程释放锁之后再竞争锁
  3. 线程A与线程B抢的不是同一把锁,它们之间没有竞争关系,分别去拿到自己的锁,不存在锁关系
g.如何判断多个线程竞争的是不是同一把锁
  1. 实例对象:new出来的对象,每个都是单独存在
  2. 类中的属性:类没有用static修饰的变量,每个实例对象都是不同的
  3. 类中的静态成员变量:用static修饰,属于类对象,全局唯一
  4. 类对象:.class文件加载jvm之后的对象,全局唯一
  5. 线程之间是否存在锁竞争,关键是看访问的是不是同一个锁对象,如果是则存在锁竞争,如果不是则不存在锁竞争
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的对⽐(⾯试题)

  1. wait需要搭配synchronized使用 sleep,join不需要
  2. wait是Object的方法,sleep是Thread的静态方法,join是类中的方法(实例方法)
  3. 一个是用于线程之间的通信的,两个是让线程阻塞一段时间
  4. 相同点:可以让线程放弃执行一段时间

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 【知识改变命运】03 多线程(3)

文章目录 多线程带来的风险-线程安全线程不安全的举例分析产出线程安全的原因&#xff1a;1.线程是抢占式的2. 多线程修改同一个变量&#xff08;程序的要求&#xff09;3. 原子性4. 内存可见性5. 指令重排序 总结线程安全问题产生的原因解决线程安全问题1. synchronized关键字…...

Flash操作 原子写 非原子写

原子和非原子操作 读、修改、写操作 对一个变量 A 1或上0x01&#xff0c;C语言写法&#xff1a; A 1| 0x01; 通过编译转成汇编后&#xff1a; LOAD R1,[#A 1] ; Read a value from A 1 into R1 MOVE R2,#0x01 ; Move the absolute constant 1 into R2 OR R1,R2 ; Bitwise O…...

厦门凯酷全科技有限公司怎么样?

随着短视频和直播带货的兴起&#xff0c;抖音电商平台迅速崛起&#xff0c;成为众多品牌和商家争夺的新战场。在这个竞争激烈的市场中&#xff0c;如何抓住机遇、实现销售增长&#xff0c;成为了每个企业面临的挑战。厦门凯酷全科技有限公司&#xff08;以下简称“凯酷全”&…...

ubuntu 18.04设置命令行历史记录并同时显示执行命令的时间

以下相关详细信息请参考ubuntu官网。 在Ubuntu 18.04中&#xff0c;查看特定用户&#xff08;例如用户broko&#xff09;的命令行历史记录&#xff0c;并同时显示执行命令的时间&#xff0c;可以通过修改用户的shell配置文件来实现&#xff1a; • 设置HISTTIMEFORMAT环境变量…...

推荐系统里面的多任务学习概述

1. 概述 多任务学习&#xff08;multi-task learning&#xff09;&#xff0c;本质上是希望使用一个模型完成多个任务的建模&#xff0c;在推荐系统中&#xff0c;多任务学习一般即指多目标学习&#xff08;multi-label learning&#xff09;&#xff0c;不同目标输入相同的fe…...

解决uview ui赋值后表单无法通过验证

微信小程序中 主要还是文档有这样一段话&#xff1a;//如果需要兼容微信小程序&#xff0c;并且校验规则中含有方法等&#xff0c;只能通过setRules方法设置规则。 添加即可通过 onReady() {//如果需要兼容微信小程序&#xff0c;并且校验规则中含有方法等&#xff0c;只能通过…...

【GL010】C/C++总结(二)

C部分 1. C中类成员的访问权限 无论成员被声明为 public、protected 还是 private&#xff0c;都是可以互相访问的&#xff0c;没有访问权限的限制。在类的外部 &#xff08;定义类的代码之外&#xff09;&#xff0c;只能通过对象访问成员&#xff0c;并且通过对象只能访问 p…...

【合作原创】使用Termux搭建可以使用的生产力环境(五)

前言 在上一篇【合作原创】使用Termux搭建可以使用的生产力环境&#xff08;四&#xff09;-CSDN博客我们讲到了如何让proot-distro中的Debian声音驱动正常&#xff0c;将我们的系统备份后&#xff0c;通过VNC客户端连接到VNC服务器&#xff0c;这一篇我们来讲一下xfce桌面的美…...

初始数据结构

程序数据结构算法 数据结构研究计算机数据&#xff08;元素&#xff09;间关系 包括数据的逻辑结构和存储结构及其&#xff08;数据间&#xff09;操作 一、基本概念 1.1数据 数据即信息的载体&#xff0c;能被输入到计算机中并且能被它识别、存储和处理的符号总称 1.2数据…...

给我的小程序加了个丝滑的搜索功能,踩坑表情包长度问题

前言 最近在用自己的卡盒小程序的时候&#xff0c;发现卡片越来越多&#xff0c;有时候要找到某一张来看看笔记要找半天&#xff0c;于是自己做了一个搜索功能&#xff0c;先看效果&#xff1a; 怎么样&#xff0c;是不是还挺不错的&#xff0c;那么这篇文章就讲讲这样一个搜索…...

MATLAB中的合并分类数组

目录 创建分类数组 串联分类数组 创建具有不同类别的分类数组 串联具有不同类别的数组 分类数组的并集 此示例演示了如何合并两个分类数组。 创建分类数组 创建分类数组 A&#xff0c;其中包含教室 A 中的 25 个学生的首选午餐饮料。 rng(default) A randi(3,[25,1]); …...

ShardingSphere-JDBC

1. 什么是分库分表&#xff1f; 分库分表是一种数据库扩展技术&#xff0c;通过将数据拆分到多个数据库&#xff08;分库&#xff09;或多个表&#xff08;分表&#xff09;中来解决单一数据库或表带来的性能瓶颈。分库分表可以有效提升系统的可扩展性、性能和高并发处理能力&…...

企业如何选择远程控制软件来远程IT运维?

在当今企业的日常运作中&#xff0c;IT运维无疑是核心环节之一&#xff0c;它对于保持企业信息系统的稳定运行和数据安全至关重要。随着科技的快速进步&#xff0c;远程控制软件在IT运维中的应用变得越来越重要。今天&#xff0c;我们就来探讨一下远程控制软件如何助力企业IT运…...

Meta Llama 3.3 70B:性能卓越且成本效益的新选择

Meta Llama 3.3 70B&#xff1a;性能卓越且成本效益的新选择 引言 在人工智能领域&#xff0c;大型语言模型一直是研究和应用的热点。Meta公司最近发布了其最新的Llama系列模型——Llama 3.3 70B&#xff0c;这是一个具有70亿参数的生成式AI模型&#xff0c;它在性能上与4050…...

【银河麒麟高级服务器操作系统】修改容器中journal服务日志存储位置无效—分析及解决方案

了解更多银河麒麟操作系统全新产品&#xff0c;请点击访问 麒麟软件产品专区&#xff1a;https://product.kylinos.cn 开发者专区&#xff1a;https://developer.kylinos.cn 文档中心&#xff1a;https://documentkylinos.cn 服务器环境以及配置 【机型】 整机类型/架构&am…...

go语言zero框架对接阿里云消息队列MQ的rabbit的配置与调用

在 Go 语言中对接阿里云消息队列&#xff08;MQ&#xff09;的 RabbitMQ 配置与调用&#xff0c;首先需要安装和配置相关的 Go 库&#xff0c;并了解如何通过 RabbitMQ 与阿里云消息队列进行交互。 ### 步骤一&#xff1a;安装 RabbitMQ Go 客户端库 阿里云的消息队列&#x…...

《Vue进阶教程》第四课:reactive()函数详解

往期内容&#xff1a; 《Vue零基础入门教程》合集&#xff08;完结&#xff09; 《Vue进阶教程》第一课&#xff1a;什么是组合式API 《Vue进阶教程》第二课&#xff1a;为什么提出组合式API 《Vue进阶教程》第三课&#xff1a;Vue响应式原理 通过前面的学习, 我们了解到r…...

【开源】A065—基于SpringBoot的库存管理系统的设计与实现

&#x1f64a;作者简介&#xff1a;在校研究生&#xff0c;拥有计算机专业的研究生开发团队&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的网站项目。 代码可以查看项目链接获取⬇️&#xff0c;记得注明来意哦~&#x1f339; 赠送计算机毕业设计600个选题ex…...

memmove函数(带图详解)

c语言系列 文章目录 c语言系列一、memmove函数介绍1.1、函数基本功能1.2、函数参数2.3、函数返回值 二、memmove的使用2.1、拷贝字节不可大于目标空间2.2、同一空间拷贝 三、函数功能的模拟实现3.1、函数参数及其返回值的设定3.2、函数体实现 四、代码实现 一、memmove函数介绍…...

【Java数据结构】时间和空间复杂度

本章开始将进入数据结构的知识&#xff0c;时间复杂度主要衡量的是一个算法的运行速度&#xff0c;而空间复杂度主要衡量一个算法所需要的额外空间&#xff0c;。 时间复杂度 算法中执行的次数决定了时间复杂度。 在计算执行次数时&#xff0c;只需要计算大概的次数&#xff…...

八斗深度学习

八斗深度学习第二周笔记 一、深度学习步骤&#xff1a;1. 选定模型结构2. 模型参数随机初始化3. 构造模型损失函数4. 选择优化算法并设置超参数5. 数据准备与预处理6. 训练模型7. 模型评估8. 测试模型9. 应用模型 损失函数极小值、导向意义 超参数的影响迭代次数epoch批次量大小…...

安卓报错Switch Maven repository ‘maven‘....解决办法

例如&#xff1a;Switch Maven repository ‘maven(http://developer.huawei.com/repo/)’ to redirect to a secure protocol 在库链接上方添加配置代码&#xff1a;allowInsecureProtocol true...

Scala编程技巧:正则表达式与隐式转换

1. 引言 在Scala编程中&#xff0c;正则表达式和隐式转换是处理字符串匹配和类型转换的强大工具。本文将通过一个实用的示例——电话号码和身份证号码验证器&#xff0c;来展示如何使用这些工具。 2. 知识概括 2.1 正则表达式基础 正则表达式是用于字符串搜索和匹配的强大工…...

UnityShaderLab 实现黑白着色器效果

实现思路&#xff1a;取屏幕像素的RGB值&#xff0c;将三个通道的值相加&#xff0c;除以一个大于值使颜色值在0-1内&#xff0c;再乘上一个强度值调节黑白强度。 在URP中实现需要开启Opaque Texture ShaderGraph实现&#xff1a; ShaderLab实现&#xff1a; Shader "Bl…...

在Windows 10中使用SSH远程连接服务器(附花生壳操作方法)

SSH 在 linux 中是一种重要的系统组件&#xff0c;用户可以使用 SSH 来远程连接 linux 系统的计算机&#xff0c;或者传输文件。不过在 win10 以前&#xff0c;windows 并不原生支持 SSH&#xff0c;需要借助第三方工具来使用 SSH 功能。而实际上&#xff0c;微软在 2015 年就曾…...

在算网云平台云端在线部署stable diffusion (0基础小白超详细教程)

Stable Diffusion无疑是AIGC领域中的AI绘画利器&#xff0c;具有以下显著优势&#xff1a; 1、开源性质&#xff0c;支持本地部署 2、能够实现对图像生成过程的精确控制 虽然SD在使用上有很多的有点&#xff0c;但缺点也是不言而喻的&#xff0c;由于AI绘画的整个过程以及现…...

ubuntu存储空间不足快速解决

几个自己常用的释放空间命令&#xff0c;备忘 将文件夹下的文件按从大到小排列 ls -lhS /var/log/syslog 过大 sudo truncate -s 0 /var/log/syslog /var/log/Xorg.0.log.old过大 sudo truncate -s 0 /var/log/Xorg.0.log.old 清理系统日志文件&#xff1a; sudo journalctl --…...

Prescan simulink carsim联合仿真平台搭建问题总结

解决办法主要来自忠厚的老王&#xff1a;自动驾驶决策规划算法第二章第一节 决策规划仿真平台搭建_哔哩哔哩_bilibili 这部分直接复制的老王视频的&#xff1a; Q1:prescan安装了&#xff0c;但是找不到Demo_Carsim3D A1:这个文件夹是我自己建立的不是prescan自带的&#xff0…...

STM32(HAL_工程模板的搭建)

目录 一、准备文件 二、创建工程 三、创建分组 四、配置文件处理 五、编译错误处理 一、准备文件 准备HAL库文件: ST官网( 意法半导体-STMicroelectronics )搜索STM32Cube, 本文使用“STM32Cube_FW_F4_V1.24.1” 版本的HAL库, 使用的是F4的库文件。 创建文件&#xff1a…...

Flask入门一(介绍、Flask安装、Flask运行方式及使用、虚拟环境、调试模式、配置文件、路由系统)

文章目录 一、Flask介绍二、Flask创建和运行 1.安装2.快速使用3.Flask小知识4.flask的运行方式 三、Werkzeug介绍四、Jinja2介绍五、Click CLI 介绍六、Flask安装 介绍watchdog使用python–dotenv使用&#xff08;操作环境变量&#xff09; 七、虚拟环境 介绍Mac/linux创建虚拟…...

网站空间备份/成都网站seo推广

转自&#xff1a;http://blog.csdn.net/zy_zhengyang/article/details/45009431 代码文件主要针对Matlab进行说明,但个人仍觉得讲解的支持向量机内容非常棒,可以做为理解这一统计方法的辅助资料; LibSVM是台湾林智仁(Chih-Jen Lin)教授2001年开发的一套支持向量机的库&#xff…...

wordpress dante 下载/网站怎么打开

今天我们来聊一聊Tomcat&#xff0c;相信大家并不陌生&#xff0c;tomcat是一个免费开源的web应用服务器&#xff0c;属于轻量级的应用程序&#xff0c;在小型生产环境和并发不是很高的场景下被普遍使用&#xff0c;同时也是开发测试JSP程序的首选。也是处理jsp动态请求不错的选…...

汕头哪里做网站/网站seo哪家做的好

计算机中级教案计算机基础知识第一部分 WINDOWS操作系统第部分商务办公软件第三部分打印机及扫描仪使用2788第一部分W| NDOWS操作系统第一节五笔输入法第二节 WINDOWS基础操作第二部分商务办公软件前言: OFFICE常识第一节WORD2003第二节 EXCEL2003第三节 POWERPOINT第四节 WPS …...

企业网站安全建设方案/东莞百度seo关键词优化

http://blog.csdn.net/slnqnd/article/details/1772910/ Struts2.0 Hibernate 3.2 Spring 2.0 一&#xff0e; Struts 1.定义 它是使用 servlet 和 JavaServer Pages 技术的一种 Model-View-Controller 实现&#xff0c; 可帮助您控制Web 项目中的变化并提高专业化水平。…...

孝感市门户网站/个人如何在百度做广告

如何選擇1個適合您的 charging ic 呢&#xff1f;主要考量以下 parameters charging ic 的 IIN, VINcharging ic 給 battery 的 IIN, VINsystem VINspecial features&#xff1a;power path&#xff0c;otg&#xff0c;jeitabattery chemistry, series or parallel  you can …...

利用博客做网站/能打开任何网站浏览器

下面由thinkphp教程栏目给大家介绍比较ThinkPHP5和无框架代码在高并发下的效率&#xff0c;希望对需要的朋友有所帮助&#xff01;测试的业务逻辑&#xff1a;测试一个抽奖功能&#xff0c;使用MySQL数据库的乐观锁机制防止超发。关键代码&#xff1a;$prizeArr array(array(l…...