目录

  • synchronized
    • 刷新内存
    • synchronized的特性可重入的
    • 出现死锁的情况
    • 如何避免死锁(重点,死锁的成因和解决)
  • volatile关键字
  • wait和notify
  • 多线程的代码案例
    • 饿汉模式和懒汉模式的线程安全问题
    • 指令重排序问题
    • 阻塞队列
      • 使用
      • 自己实现一个阻塞队列
      • 实现生产者消费者模型

synchronized

  1. synchronized除了修饰代码块之外,还可以修饰一个实例方法或者是修饰一个静态方法

synchronized修饰实例方法

class Counter{public int count;// 此方法是下面方法的简化版本synchronized public void increase(){count++;}// this作为了锁对象public void increase2(){synchronized(this){count++;}}
}
public class Demo14 {public static void main(String[] args) throws InterruptedException {Counter counter = new Counter();Thread t1 = new Thread(()->{for(int i = 0;i < 50000;i++){counter.increase();}});Thread t2 = new Thread(()->{for(int i = 0;i < 50000;i++){counter.increase();}});t1.start();t2.start();t1.join();t2.join();System.out.println(counter.count);}
}

在这里插入图片描述
synchronized修饰静态方法

在这里插入图片描述
java的对象头:java的一个对象的内存空间中,除了你自己定义的属性之外,还有一些自带的属性,这些自带的属性就叫对象头
在对象头中,其中就有属性表示当前对象是否已经加锁
synchronized使用的是java对象头当中的锁

刷新内存

  1. synchronized刷新内存存疑,网上众说纷纭

synchronized的特性可重入的

  1. 可重入锁:指的是一个线程连续针对一把锁加锁两次,不会出现死锁满足这个要求就是可重入的,不满足就是不可重入

  2. 死锁:就是同一个线程对同一个对象加锁两次,第二次的加锁需要第一次的解锁,而第一次的解锁需要第二次的加锁完毕,所以两者相互矛盾
    在这里插入图片描述

  3. 死锁在日常的代码中出现,还不容易观察到,比如下面的代码,使用可重入锁可以解决死锁的问题

  4. 可重入锁:让锁记录一下,是哪个线程给它锁住的,后续再加锁的时候,如果加锁线程就是持有锁的线程就直接加锁成功
    在这里插入图片描述

  5. 提一个问题:
    不能释放锁,因为两个右大括号中可能有别的代码,解锁了就线程不安全了
    在这里插入图片描述

  6. 利用引用计数,记录释放锁的时机
    在这里插入图片描述

出现死锁的情况

  1. 两种情况:
    在这里插入图片描述
  2. 两个线程,两把锁
public class Demo15 {public static void main(String[] args) {Object locker1 = new Object();Object locker2 = new Object();Thread t1 = new Thread(()->{synchronized (locker1){// 这里的sleep非常重要,要确保t1 和 t2 都分别拿到一把锁之后,再进行后续动作// 否则就会出现t1很快对两把锁都加锁了的情况,就不会出现死锁的情况了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();}
}

在这里插入图片描述
在这里插入图片描述
3. N个线程,M把锁(相当于2的扩展)
这样更容易出现死锁了
有一个经典的描述N个线程,M把锁的模型,哲学家就餐问题

在这里插入图片描述

如何避免死锁(重点,死锁的成因和解决)

  1. 死锁的原因:
    死锁有4个必要条件:
    在这里插入图片描述
    第4点也是(代码结构)
    第4点循环等待比如是哲学家就餐问题

  2. 第1和第2点是锁的基本特性,只要3和4出现了就会形成死锁

解决死锁问题:
1.对于1和2是synchronized本身的特性,解决不了
2.对于3,请求等待可以把代码改成并列执行的顺序,先执行1再执行2
3.对于4,循环等待,可以规定小的编号的先执行,再执行大的编号(先大后小也可以)
4.有的时候必须要获取多个锁再操作,就需要编号

解决死锁条件3

public class Demo15 {public static void main(String[] args) {Object locker1 = new Object();Object locker2 = new Object();Thread t1 = new Thread(()->{synchronized (locker1){// 这里的sleep非常重要,要确保t1 和 t2 都分别拿到一把锁之后,再进行后续动作// 否则就会出现t1很快对两把锁都加锁了的情况,就不会出现死锁的情况了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();}
}

解决死锁条件4

public class Demo15 {public static void main(String[] args) {Object locker1 = new Object();Object locker2 = new Object();Thread t1 = new Thread(()->{synchronized (locker1){// 这里的sleep非常重要,要确保t1 和 t2 都分别拿到一把锁之后,再进行后续动作// 否则就会出现t1很快对两把锁都加锁了的情况,就不会出现死锁的情况了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();}
}

volatile关键字

  1. volatile用来解决线程不安全的两点
    保证内存可见性
    禁止指令重排序

内存可见性出现的线程安全问题:

import java.util.Scanner;public class Demo16 {private static int isQuit = 0;public static void main(String[] args) {// 读Thread t1 = new Thread(()->{while(isQuit == 0){// 循环里面什么都不做}System.out.println("t1线程结束!");});t1.start();// 修改isQuit的值使线程t1结束Thread t2 = new Thread(()->{System.out.println("请输入 isQuit的值:");Scanner scanner = new Scanner(System.in);isQuit = scanner.nextInt();});t2.start();}
}

在这里插入图片描述
在这里插入图片描述

出现内存可见性的原因:是编译器优化出现的bug
在这里插入图片描述
volatile可以解决上述问题
在多线程的环境下,编译器对于是否要进行优化,判定不一定准,这就需要我们使用volatile关键字告诉编译器,不要优化了
让判断isQuit和从内存中读达到平衡,让判断的操作每次读取isQuit

只需要给isQuit前加上volatile就可以了

import java.util.Scanner;public class Demo16 {private volatile static int isQuit = 0;public static void main(String[] args) {// 读Thread t1 = new Thread(()->{while(isQuit == 0){// 循环里面什么都不做// 什么都不做会飞快的运行多次}System.out.println("t1线程结束!");});t1.start();// 修改isQuit的值使线程t1结束Thread t2 = new Thread(()->{System.out.println("请输入 isQuit的值:");Scanner scanner = new Scanner(System.in);isQuit = scanner.nextInt();});t2.start();}
}

编译器优化,加了sleep就不会触发内存可见性问题了,原因如下:
在这里插入图片描述

  1. 内存的可见性问题:
    在这里插入图片描述
    main memory:主存
    work memory:cpu寄存器 + 缓存

  2. volatile不能保证原子性

  3. synchronized可以保证内存可见性

  4. volatile可以解决的线程安全问题主要是可见性问题和有序性问题,但不能解决原子性问题。

wait和notify

  1. wait:等待,让指定线程进入阻塞状态
  2. notify:通知,唤醒阻塞状态的进程
    在这里插入图片描述
    下面的代码就是在t2线程中,t1进行join,t2线程执行后,t2线程要等待t1线程执行完毕之后才能继续执行
public class Demo17 {public static void main(String[] args) {Thread t1 = new Thread(()->{for(int i = 0;i < 5;i++){try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("t1线程执行完毕!");});Thread t2 = new Thread(()->{try {// join()在哪个线程中,哪个线程就会等待调用join()的线程先执行完毕,该线程才能继续执行t1.join();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("t2线程执行完毕!");});t1.start();t2.start();System.out.println("主线程执行完毕!");}
}

在这里插入图片描述

  1. wait在执行的时候要做三件事:
    释放当前的锁
    让线程进入阻塞
    当线程被唤醒的时候,重新获取到锁

释放锁的前提是先加上锁
wait和notify都是Object的方法
随便一个对象都可以使用wait和notify

wait的代码,等待

public class Demo18 {public static void main(String[] args) throws InterruptedException {Object object = new Object();synchronized(object) {System.out.println("调用wait之前");// 把wait放在synchronized里面来调用,确实是先拿到了锁object.wait();// 然后阻塞等待,等待到其他线程调用nofity唤醒System.out.println("调用wait之后");}}
}

在这里插入图片描述
notify的代码,等待唤醒

public class Demo19 {public static void main(String[] args) {// 必须对同一个对象进行等待唤醒Object object = new Object();Thread t1 = new Thread(()->{synchronized (object){System.out.println("wait开始!");try {object.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("wait结束!");}});Thread t2 = new Thread(()->{synchronized (object){try {Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}object.notify();System.out.println("wait唤醒!");}});t1.start();t2.start();}
}
  1. 使用wait和notify也可以避免线程饿死

线程饿死:让1号一直在获取锁和释放锁之间反复横跳
在这里插入图片描述
解决方法:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
5. wait还有一个超时时间的版本,避免了wait无休止的等待下去,比如:wait(3000)

多线程的代码案例

  1. 单例模式:非常经典的设计模式
    设计模式:就是程序员的棋谱,开发过程中会遇到很多经典场景,针对这些场景,大佬就提出了很多解决方案,按照解决方案来写就不会很差

2. 最容易考的两种模式:单例模式和工厂模式

  1. 单例:单个实例(对象),有些场景下,我们希望有的类,只能有一个对象,使用单例模式
  2. 让编译器强制要求,只能new一次对象
  3. 单例模式语法上没有要求,要通过编程技巧来达到

在这里插入图片描述
单例模式:
1.在类的内部,自己提供一个现有的实例
2.构造方法设置为private修饰的,不让其他人进行创建新的实例

class Singleton{// 自己提供一个现有的实例private static Singleton instance = new Singleton();// 通过这个方法来获取这个实例public static Singleton getInstance(){return instance;}// 把它设置为私有,这样外面的其他代码就无法new出这个实例了private Singleton(){}
}
public class Demo20 {public static void main(String[] args) {// 这里又有一个实例了,就不是单例了// Singleton singleton = new Singleton();Singleton s1 = Singleton.getInstance();Singleton s2 = Singleton.getInstance();System.out.println(s1 == s2);}
}

在类加载的时候创建,饿汉模式(比较急切)
在这里插入图片描述
懒汉模式(在第一次使用的时候,再去创建实例
和饿汉模式的区别是:在调用getInstance()的时候才创建出实例
也只有唯一实例

// 懒汉模式
class SingletonLazy{public static SingletonLazy instance = null;public static SingletonLazy getInstance(){if(instance == null){instance = new SingletonLazy();}return instance;}private SingletonLazy(){}
}
public class Demo21 {public static void main(String[] args) {}
}

懒汉模式其实在计算机当中是更加高效的,下面就是一个例子:
在这里插入图片描述

饿汉模式和懒汉模式的线程安全问题

  1. 线程安全问题造成的原因:如果多个线程同时修改同一个变量,会出现线程安全问题
    如果多个线程同时读取同一个变量,不会有线程安全问题

  2. 饿汉模式只读取了变量,是安全的

  3. 懒汉模式既读取了变量,又修改了变量是不安全的

不安全的原因:
在这里插入图片描述
如何保证懒汉模式是线程安全的?
需要再if的前面进行加锁,让它的操作变成原子的操作
在这里插入图片描述

在这里插入图片描述
一旦出现加锁,那就不是高性能了
在这里插入图片描述
我感觉是只加锁一次就行,后续,再使用这个获取实例的方法就不加锁,可以吗?

有什么办法既保证线程安全,又可以保证不怎么影响执行效率呢?

可以再加一个if
第一个if用来判定是否需要加锁
第二个if用来判定是否需要new对象,保证对象只new一次

// 懒汉模式
class SingletonLazy{public static SingletonLazy instance = null;public static SingletonLazy getInstance(){// 懒汉模式对类对象进行加锁// if和new的问题// 如果线程安全,就是已经new好了一个对象// 如果线程不安全(有不安全的风险),还没有newif(instance == null) {synchronized (SingletonLazy.class) {if (instance == null) {instance = new SingletonLazy();}return instance;}}}private SingletonLazy(){}
}
public class Demo21 {public static void main(String[] args) {}
}

在这里插入图片描述
在这里插入图片描述

指令重排序问题

  1. 指令重排序也是编译器的优化,编译器为了提高效率,会调整原有代码的执行顺序,前提是保证了逻辑是不变的
  2. 指令重排序可以再保证逻辑不变的前提下,提高代码的执行效率
  3. 指令重排序可能会对我们的代码产生影响,单线程下可以,多线程对逻辑不变会产生误判

在这里插入图片描述
4. 使用volatile可以解决指令重排序问题
5. 如果日常使用过程中少了1,2,3其中一个都会出现问题
在这里插入图片描述
6. 使用反射能否打破单例模式
使用序列化/反序列化能否打破单例模式
其实反射和序列化都是在特定场景下使用的,使用的比较克制

单例模式在面试过程中都是经常考到的
先写一个线程不安全的,再写一个加锁的,再写一个高性能,不要每次加锁的,最后写一个考虑指令重排序问题的

// 懒汉模式
class SingletonLazy{public static volatile SingletonLazy instance = null;public static SingletonLazy getInstance() {// 懒汉模式对类对象进行加锁// if和new的问题// 如果线程安全,就是已经new好了一个对象// 如果线程不安全(有不安全的风险),还没有newif (instance == null) {synchronized (SingletonLazy.class) {if (instance == null) {instance = new SingletonLazy();}}}return instance;}private SingletonLazy(){}
}
public class Demo21 {public static void main(String[] args) {}
}

阻塞队列

  1. 阻塞队列是多线程代码中比较常用到的一种数据结构
  2. 阻塞队列最大的意义就是可以用来实现生产者消费者模型
  3. 生产者消费者模型,一种常见的多线程代码编写方式

阻塞队列
1.线程安全的
2.带有阻塞特性的:
如果队列为空,再往队列中出元素,就会阻塞,就要一直阻塞到其他线程往队列中添加元素为止

如果队列为满,再往队列中入元素,就会阻塞,就要一直阻塞到其他线程往队列中取出元素为止

在这里插入图片描述
3. 生产者消费者模型的好处(意义)是什么?
可以降低资源的竞争,提高我们程序的效率

在这里插入图片描述

解耦合
比如服务器A和服务器B进行数据交互,A用来发送请求,B用来接收请求,A和B的耦合度比较高,如果B挂了的话,会影响到A,如果再加入一个C服务器,也和A交互,A的代码就需要进行修改

如果使用一个阻塞队列的话,A和B都把请求和响应放入阻塞队列中,A挂了不会影响B,再加入一个C也不需要修改A的代码

这样耦合度就降低了

在这里插入图片描述
2. 削峰填谷
峰:短时间内请求量比较多
谷:请求量比较少
有了这样的机制之后,就可以在有突发情况来临时,整个服务器系统让然可以正确执行

在这里插入图片描述

利用生产者消费者模型也可以得到解决
在这里插入图片描述

使用

阻塞队列
在这里插入图片描述

import jdk.nashorn.internal.ir.Block;import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;public class Demo22 {public static void main(String[] args) throws InterruptedException {BlockingQueue<String> queue = new LinkedBlockingQueue<>();queue.put("111");queue.put("222");queue.put("333");queue.put("444");System.out.println(queue.take());System.out.println(queue.take());System.out.println(queue.take());System.out.println(queue.take());// 最后一次会阻塞住System.out.println(queue.take());}
}

自己实现一个阻塞队列

  1. 基于一个普通的队列,加上线程安全,加上阻塞就可以了
    在这里插入图片描述
  2. 使用size来判空和判满
    普通的队列
    修改会产生线程安全问题,直接加锁
    使用wait和notify实现阻塞

彼此唤醒对方的wait,一个队列要么是空,要么是满

// 不写作泛型了,直接让队列存储String
class MyBlockingQueue{// 此处也可以使用构造方法,来指定数组的最大长度private String[] data = new String[1000];// 队列的起始位置// 都加上volatile,防止出现内存可见性问题private volatile int head = 0;// 队列的结束位置的下一个位置private volatile int tail = 0;// 队列中的有效元素个数private volatile int size = 0;// 入队列和出队列public void put(String elem) throws InterruptedException {synchronized (this) {while (size == data.length) {// 满了,再插入就阻塞了// 普通队列,直接returnthis.wait();}data[tail] = elem;tail++;// 如果自增之后来到了数组末尾,让它回到数组的开头,环形队列if (tail == data.length) {tail = 0;}size++;// 这个notify用来唤醒take中的waitthis.notify();}}public String take() throws InterruptedException {synchronized (this) {while (size == 0) {// 队列为空,直接返回// 队列为空,继续出队列就会出现阻塞this.wait();}String ret = data[head];head++;size--;if (head == data.length) {head = 0;}// 这个notify用来唤醒put中的waitthis.notify();return ret;}}
}

异常唤醒的
wait还可能被interrupt唤醒,interrupt会中断wait
在这里插入图片描述
在这里插入图片描述
处理异常唤醒的情况:
在这里插入图片描述
加上volatile
在这里插入图片描述

实现生产者消费者模型

  1. 用阻塞队列来实现生产者消费者模型
// 不写作泛型了,直接让队列存储String
class MyBlockingQueue{// 此处也可以使用构造方法,来指定数组的最大长度private String[] data = new String[1000];// 队列的起始位置// 都加上volatile,防止出现内存可见性问题private volatile int head = 0;// 队列的结束位置的下一个位置private volatile int tail = 0;// 队列中的有效元素个数private volatile int size = 0;// 入队列和出队列public void put(String elem) throws InterruptedException {synchronized (this) {while (size == data.length) {// 满了,再插入就阻塞了// 普通队列,直接returnthis.wait();}data[tail] = elem;tail++;// 如果自增之后来到了数组末尾,让它回到数组的开头,环形队列if (tail == data.length) {tail = 0;}size++;// 这个notify用来唤醒take中的waitthis.notify();}}public String take() throws InterruptedException {synchronized (this) {while (size == 0) {// 队列为空,直接返回// 队列为空,继续出队列就会出现阻塞this.wait();}String ret = data[head];head++;size--;if (head == data.length) {head = 0;}// 这个notify用来唤醒put中的waitthis.notify();return ret;}}
}public class Demo23 {public static void main(String[] args) {// 生产者和消费者分别用一个线程来表示(也可以用多个)MyBlockingQueue queue = new MyBlockingQueue();// 消费者Thread t1 = new Thread(()->{while(true){try {String result = queue.take();System.out.println("消费元素 = " + result);Thread.sleep(500);// 先不sleep,利用sleep控制生产和消费的速度} catch (InterruptedException e) {throw new RuntimeException(e);}}});// 生产者Thread t2 = new Thread(()->{int num = 1;while(true) {try {queue.put(num + " ");System.out.println("生产元素 = " + num);num++;// Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}}});t1.start();t2.start();}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/96105.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/96105.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/96105.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySql 内外连接

1.内连接内连接实际上就是利用where子句对两种表形成的笛卡儿积进行筛选&#xff0c;我们前面学习的查询都是内连 接&#xff0c;也是在开发过程中使用的最多的连接查询。 语法&#xff1a;select 字段 from 表1 inner join 表2 on 连接条件 and 其他条件&#xff1b;备注&…

【大前端】 断点续传 + 分片上传(大文件上传优化) 的前端示例

写一个 断点续传 分片上传&#xff08;大文件上传优化&#xff09; 的前端示例。这样即使网络中断&#xff0c;文件也可以从已上传的部分继续传&#xff0c;不需要重新传整个大文件。&#x1f539; 分片上传 断点续传思路分片切割&#xff1a;把大文件切成固定大小的小块&…

机器学习的发展与应用:从理论到现实

目录 引言 一、机器学习的发展历程 1. 萌芽阶段&#xff08;1950s–1970s&#xff09; 2. 符号主义与统计学习阶段&#xff08;1980s–1990s&#xff09; 3. 数据驱动与算法突破&#xff08;2000s–2010s&#xff09; 4. 深度学习崛起&#xff08;2012年至今&#xff09; …

Python实现讯飞星火大模型Spark4.0Ultra的WebSocket交互详解

核心架构设计与初始化机制 代码采用面向对象的设计模式构建了Ws_Param类作为核心配置载体。该类在初始化时接收四个关键参数:APPID(应用标识)、APIKey(接口密钥)、APISecret(签名秘钥)和Spark_url(服务端点地址)。通过urlparse模块解析URL结构,分离出主机名(host)与…

如何通过Linux在高通跃龙QCS6490 平台上优化部署AI/ML模型?

简介 高通于今年推出了高通跃龙&#xff0c;在边缘提供前沿的AI性能和超低延迟&#xff0c;为可扩展的工业创新带来新的可能性。研华已在各种规格尺寸的嵌入式方案中采用跃龙技术&#xff0c;包括由高通跃龙 QCS6490处理器支持的嵌入式模块、单板电脑和AI摄像头解决方案。研华…

MySQL内核革新:智能拦截全表扫描,百度智能云守护数据库性能与安全

在日常数据库运维中&#xff0c;“扫表风暴”数次悄然而至——某条未走索引的 SQL 突然执行全表扫描&#xff0c;短短几分钟内吃光 IO、拖高 CPU&#xff0c;最终引发集群抖动甚至服务不可用。这样的事故&#xff0c;你是否也曾经历过&#xff1f; 全表扫描&#xff08;Full Ta…

TCP 三次握手、四次挥手

三次握手 三次握手形象版&#xff0c;快速理解 deepseek 的象形比喻&#xff1a;三次握手建立连接就像打电话一样&#xff1a; (1) A 打给 B&#xff0c;“喂&#xff0c; 你能听到我说话吗&#xff1f;” (2) B 回答 A&#xff0c;“嗯&#xff0c;可以听到&#xff0c;你能听…

数据管理战略|1概念及组成部分

【小语】前面两个文章讲到了“数据管理战略数字化转型、数据驱动”三者之间关系,数字化改革中的原则与逻辑,本节用三次文章学习数据管理战略内容的组成部分(DAMA数据管理第1章1.2.6节)。 数据战略 VS 数字化转型 VS 数据驱动 数据管理战略|熵减与熵增相容原则 下文为【…

3.远程控制网络编程的设计上

RemoteCtrl.cpp// RemoteCtrl.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 //#include "pch.h" #include "framework.h" #include "RemoteCtrl.h"#ifdef _DEBUG #define new DEBUG_NEW #endif// 唯一的应用程序对象C…

毕业设计|基于Python的课程智能问答系统

4系统设计4.1功能模块设计对本系统进行全面的系统功能的分析&#xff0c;可以得出基于Python《Python程序设计》课程智能问答系统的功能模块图&#xff0c;如图4-1所示。图4-1 系统功能模块图4.2数据库设计4.2.1数据库设计原则学习程序设计时&#xff0c;若想要深入理解数据库管…

iOS原生开发和Flutter开发的看法

这是一个技术选型的问题。作为一名同时精通iOS原生和Flutter的开发者&#xff0c;我的看法是&#xff1a;这不是一个“二选一”的问题&#xff0c;而是一个“如何根据场景做最佳选择”的问题。 它们不是替代关系&#xff0c;而是互补关系。以下是我对两者的对比和看法&#xff…

docker桌面版 镜像配置

配置内容 Docker Engine配置*&#xff08;截止2025年09月10日能用&#xff09; {"builder": {"gc": {"defaultKeepStorage": "20GB","enabled": true}},"experimental": false,"registry-mirrors": [&q…

Java 面向对象基础初步

Java 面向对象基础初步 面向对象的核心概念概览 面向对象的核心目标是 把数据和操作封装在一起&#xff08;对象&#xff09;&#xff0c;并通过抽象、继承与多态组织程序。简而言之&#xff0c;我们总是没法回避程序设计的四个话题&#xff1a; 封装&#xff08;Encapsulation…

反向代理技术

一、核心比喻&#xff1a;公司的总机前台 想象一下一家大公司&#xff1a; 客户&#xff1a;想联系公司里的某位员工&#xff08;比如技术部的张三&#xff09;。公司的总机号码&#xff08;唯一公开的号码&#xff09;&#xff1a;比如 400-123-4567。前台&#xff1a;接听总机…

数据整理器(Data Collators)(90)

数据整理器(Data Collators) 数据整理器(Data Collators) 导致问题的“罪魁祸首”,往往是长度不一的序列。 指令格式 关键术语说明 数据整理器(Data Collators) 数据整理器负责将多个数据样本拼接成一个迷你批次(mini-batch)。它通常处于“隐形”状态——每次使用PyT…

PySpark EDA 完整案例介绍,附代码(三)

本篇文章Why Most Data Scientists Are Wrong About PySpark EDA — And How to Do It Right适合希望高效处理大数据的从业者。文章的亮点在于强调了使用PySpark进行探索性数据分析&#xff08;EDA&#xff09;的重要性&#xff0c;避免了将Spark数据框转换为Pandas的低效做法。…

leetcode18(无重复字符的最长子串)

给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长 子串 的长度。示例 1:输入: s "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 "abc"&#xff0c;所以其长度为 3。思路&#xff1a;对于长度为0的数组单独处理其他数组最小的可能…

计算机毕设 java 高校家教平台 基于 SSM 框架的高校家教服务平台 Java+MySQL 的家教预约与课程管理系统

计算机毕设java高校家教平台75snd9 &#xff08;配套有源码 程序 mysql数据库 论文&#xff09;本套源码可以先看具体功能演示视频领取&#xff0c;文末有联xi 可分享在高校家教需求增长的背景下&#xff0c;传统家教对接依赖线下中介、信息分散&#xff0c;存在沟通成本高、课…

【自记】Python 的 SQLAlchemy 完整实践教程

目录 SQLAlchemy 介绍环境准备与安装数据库连接数据模型定义基本数据操作复杂查询操作高级特性实战项目示例性能优化与最佳实践常见问题与解决方案 1. SQLAlchemy 介绍 1.1 什么是SQLAlchemy SQLAlchemy 是一个用于 Python 的 SQL 工具和对象关系映射&#xff08;ORM&#x…

springboot rabbitmq 延时队列消息确认收货订单已完成

供应商后台-点击发货-默认3天自动收货确认&#xff0c;更新订单状态已完成。1 pom.xml 引入依赖&#xff1a;<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId></dependency>2 …