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

单例模式与多线程

目录

前言

正文

1.立即加载/饿汉模式 

2.延迟加载/懒汉模式 

1.延迟加载/懒汉模式解析 

2.延迟加载/懒汉模式的缺点 

3.延迟加载/懒汉模式的解决方案 

(1)声明 synchronized 关键字

(2)尝试同步代码块

(3)针对某些重要的代码进行单独的同步

(4)使用 DCL 双检查锁机制

(5)双检查锁 DCL 使用 volatile 的必要性 

3.使用静态内置类实现单例模式 

4.序列化和反序列化的单例模式实现 

5.使用 static 静态代码块实现单例模式 

6.使用 enum 枚举类型实现单例模式 

7.完善使用 enum 枚举类实现单例模式 

总结


 

前言

在单例模式与多线程技术相结合的过程中,我们能发现许多以前从未考虑过的问题。这些不良的程序设计如果应用在商业项目中将会带来非常大的麻烦。线程与某些技术相结合时,我们需要考虑的事情会更多。总的来说,在本节我们只需要考虑一件事,那就是:如何使单例模式与多线程结合时是安全的、正确的。


正文

在标准的23个设计模式中,单例模式在应用中是比较常见的。但多数常规的该模式教学并没有结合多线程技术进行介绍,这就造成在使用结合多线程的单例模式时会出现一些意外。这样的代码如果在生产环境中出现异常,有可能造成灾难性的后果。 

1.立即加载/饿汉模式 

什么是立即加载?立即加载就是使用类的时候已经将对象创建完毕。常见的实现办法就是 new 实例化。从中文的语境上来看,就是 “着急” “急迫” 的含义,所以也被称为 “饿汉模式”。 

实现代码:

public class MyObject {//立即加载方式 == 饿汉模式private static MyObject myObject = new MyObject();private MyObject() {}public static MyObject getInstance(){return myObject;}}public class MyThread extends Thread{@Overridepublic void run() {System.out.println(MyObject.getInstance().hashCode());}
}public class Run {public static void main(String[] args) {MyThread t1 = new MyThread();MyThread t2 = new MyThread();MyThread t3 = new MyThread();t1.start();t2.start();t3.start();}
}

程序运行结果如图:

控制台打印的 hashCode 是同一个值,说明对象是同一个,也就实现了立即加载型单例模式。

此代码版本是立即加载模式,缺点是不能有其他实例变量,因为 getInstance() 方法没有同步,所以有可能出现非线程安全问题,比如出现如下代码:

public class MyObject {//立即加载方式 == 饿汉模式private static MyObject myObject = new MyObject();private MyObject() {}private static String username;private static String password;public static MyObject getInstance(){username = "从不同的服务器取出值(有可能不一样),并赋值";password = "从不同的服务器取出值(有可能不一样),并赋值";//上面的赋值并没有被同步,所以极易出现非线程安全问题,导致变量值被覆盖。return myObject;}}

2.延迟加载/懒汉模式 

什么是延迟加载》延迟加载就是调用 get() 方法时,实例才被工厂创建。常见的实现办法就是在 get() 方法中进行 new 实例化。 延迟加载从中文的预警来看,是 “缓慢” “不急迫” 的含义,所以也被称为 “懒汉模式”。 

1.延迟加载/懒汉模式解析 

实现代码:

public class MyObject {private static MyObject myObject;public MyObject() {}public static MyObject getInstance(){//延迟加载if (myObject != null){}else {myObject = new MyObject();}return myObject;}
}public class MyThread extends Thread{@Overridepublic void run() {System.out.println(MyObject.getInstance().hashCode());}
}public class Run {public static void main(String[] args) {MyThread t1 = new MyThread();t1.start();MyThread t2 = new MyThread();t2.start();}
}

程序运行结果如图:

此代码虽然取得一个对象的实例,但在多线程环境中会出现取出多个实例的情况,与单例模式的初衷是违背的。 

2.延迟加载/懒汉模式的缺点 

前面两个实验虽然使用了 "立即加载" 和 "延迟加载" 实现了单例模式,但在多线程环境中,"延迟加载" 示例中的代码完全是错误的,跟本不能保持单例的状态。下面来看如何在多线程环境中结合错误的单例模式创建出多个实例的。 

public class MyObject {private static MyObject myObject;private MyObject(){}public static MyObject getInstance(){try {if (myObject != null){}else {//模拟创建对象之前做一些准备工作Thread.sleep(3000);myObject = new MyObject();}} catch (InterruptedException e) {e.printStackTrace();}return myObject;}}public class MyThread extends Thread{@Overridepublic void run() {System.out.println(MyObject.getInstance().hashCode());}
}public class Run {public static void main(String[] args) {MyThread t1 = new MyThread();MyThread t2 = new MyThread();MyThread t3 = new MyThread();t1.start();t2.start();t3.start();}
}

程序运行结果如图所示:

控制台打印出 3 种 hashCode,说明创建出 3 个对象,并不是单例的,这就是 "错误的单例模式",如何解决呢?下面看一下解决方案。

3.延迟加载/懒汉模式的解决方案 

(1)声明 synchronized 关键字

既然多个线程可以同时进入 getInstance() 方法,我们只需要对 getInstance() 方法声明 synchronized 关键字即可。

public class MyObject {private static MyObject myObject;private MyObject(){}//设置同步方法效率太低//整个方法被上锁synchronized public static MyObject getInstance(){try {if (myObject != null){}else {//模拟创建对象之前做一些准备工作Thread.sleep(3000);myObject = new MyObject();}} catch (InterruptedException e) {e.printStackTrace();}return myObject;}
}public class MyThread extends Thread{@Overridepublic void run() {System.out.println(MyObject.getInstance().hashCode());}
}public class Run {public static void main(String[] args) {MyThread t1 = new MyThread();MyThread t2 = new MyThread();MyThread t3 = new MyThread();t1.start();t2.start();t3.start();}
}

运行结果如图:

此方法在加入同步 synchronized 关键字后得到相同实例的对象,但运行效率非常低。下一个线程想要取得对象,必须等上一个线程释放完锁之后,才可以执行。那换成同步代码块可以解决吗?

(2)尝试同步代码块

创建测试用例

public class MyObject {private static MyObject myObject;private MyObject(){}//设置同步方法效率太低//整个方法被上锁public static MyObject getInstance(){try {//与同步方法等同//效率一样很低,并不能减少锁的粒度// (已经是最小的范围了,必须要在if判断前加锁,不然进入else还是会创建多个对象)// 全部代码同步运行synchronized (MyObject.class) {if (myObject != null){}else {//模拟创建对象之前做一些准备工作Thread.sleep(3000);myObject = new MyObject();}}} catch (InterruptedException e) {e.printStackTrace();}return myObject;}
}public class MyThread extends Thread{@Overridepublic void run() {System.out.println(MyObject.getInstance().hashCode());}
}
public class Run {public static void main(String[] args) {MyThread t1 = new MyThread();MyThread t2 = new MyThread();MyThread t3 = new MyThread();t1.start();t2.start();t3.start();//此版本代码虽然是正确的//但全部代码都是同步的,这样做也有损效率}
}

运行结果如图 :

此方法在加入同步 synchronized 语句块后得到相同实例的对象,但运行效率也非常低,和synchronized 同步方法一样是同步运行的。下面继续更改代码,尝试解决这个问题。

(3)针对某些重要的代码进行单独的同步

同步代码块可以仅针对某些重要的代码进行单独的同步,这可以大幅度提升效率 。

创建代码如下:

public class MyObject {private static MyObject myObject;private MyObject(){}public static MyObject getInstance(){try {if (myObject != null){}else {//模拟创建对象之前做一些准备工作Thread.sleep(3000);//使用部分代码被上锁//但还是有非线程安全问题//多次创建 MyObject 类的对象,结果并不是单例synchronized (MyObject.class) {myObject = new MyObject();}}} catch (InterruptedException e) {e.printStackTrace();}return myObject;}
}public class MyThread extends Thread{@Overridepublic void run() {System.out.println(MyObject.getInstance().hashCode());}
}public class Run {public static void main(String[] args) {MyThread t1 = new MyThread();MyThread t2 = new MyThread();MyThread t3 = new MyThread();t1.start();t2.start();t3.start();}
}

程序结果如图:

 此方法使同步 synchronized 语句块只对实例化对象的关键代码进行同步。从语句的结构上讲,运行的效率的确得到了提升 ,但遇到多线程情况还是无法得到同一个实例对象。到底如何解决懒汉模式下的多线程情况呢?

(4)使用 DCL 双检查锁机制

下面使用 DCL 双重检查锁机制来实现多线程环境中的延迟加载单例模式。 

  1. 第一次检查(无锁操作): 检查实例是否已经被创建,如果已经被创建,则直接返回实例,可以减少不必要的锁竞争(即每次都进入synchronized中);
  2. 第二次检查(锁内操作): 如果第一次检查发现实例尚未创建,代码会进入一个同步块,但在创建实例之前,会再次检查实例是否被创建。这是必要的,因为在当前线程进入同步块之前,可能有另一个线程已经创建了实例。
  • 性能优化:通过减少同步的使用,DCL减少了不必要的性能开销,因为实例一旦被创建后,就不再需要同步。
  • 线程安全:通过同步块确保在实例未初始化时,只有一个线程能创建单例实例,保持了单例的线程安全约定。
  • 资源利用最优化:由于同步只在实际需要时才会发生,因此在资源利用上比始终同步要有效得多。

要注意的是,在Java中使用DCL的时候还需要考虑Java内存模型的因素。在多线程环境下,为了确保DCL正确地工作,单例对象的引用需要被声明为 volatile,这样可以防止指令重排序可能导致的DCL失效问题。

public class MyObject {private volatile static MyObject myObject;private MyObject(){}//设置同步方法效率太低//整个方法被上锁public static MyObject getInstance(){try {if (myObject != null){}else {//模拟创建对象之前做一些准备工作Thread.sleep(3000);synchronized (MyObject.class) {if (myObject==null){myObject = new MyObject();}}}} catch (InterruptedException e) {e.printStackTrace();}return myObject;}//此版本的代码称为://双重检查 Double-Check
}

使用 volatile 修饰变量 myObject ,使该变量在多个线程间可见,另外禁止 myObect = new MyObject() 代码重排序。myObject = new MyObject(); 代码包含 3 个步骤。

  1. memory = allocate(); //分配对象的内存空间
  2. ctorInstance(memory); //初始化对象
  3. myObject = memory;    //设置 instance 指向刚分配的内存地址

JIT 编译器有可能将这三个步骤重排序成。

  1. memory = allocate(); //分配对象的内存空间
  2. myObject = memory;    //设置 instance 指向刚分配的内存地址
  3. ctorInstance(memory); //初始化对象

这时,构造方法虽然还没有执行,但 myObject 对象已具有内存地址,即值不是 null。当访问 myObject 对象中的值时,是当前声明数据类型的默认值,此知识点在后面的章节中有讲解。

创建线程类的代码如下:

public class MyThread extends Thread{@Overridepublic void run() {System.out.println(MyObject.getInstance().hashCode());}
}

创建运行类的代码如下:

public class Run {public static void main(String[] args) {MyThread t1 = new MyThread();MyThread t2 = new MyThread();MyThread t3 = new MyThread();t1.start();t2.start();t3.start();}
}

运行结果如图所示:

可见 DCL 双检查锁成功解决了懒汉模式下的多线程问题。DCL 也是大多数线程结合单例模式使用的解决方案 。

(5)双检查锁 DCL 使用 volatile 的必要性 

 前面介绍了 myObject = new MyObject() 代码中的 3 个步骤会发生重排序,导致取得实例变量的值不是构造方法初始化后的值。下面开始验证。

创建测试用例 

package org.example.singleton;import java.util.Random;
import java.util.concurrent.CountDownLatch;public class dcl_and_volatile {static class OneInstanceService {public int i_am_has_state = 0;private volatile static OneInstanceService test;public OneInstanceService() {this.i_am_has_state = new Random().nextInt(200) + 1;}public static OneInstanceService getTest1() {if (test == null) {synchronized (OneInstanceService.class) {if (test == null) {test = new OneInstanceService();}}}return test;}public static void reset() {test = null;}}public static void main(String[] args) throws InterruptedException {for (; ; ) {//允许一个或多个线程等待直到在其他线程中执行的一组操作完成的同步辅助。CountDownLatch latch = new CountDownLatch(1);CountDownLatch end = new CountDownLatch(100);for (int i = 0; i < 100; i++) {Thread t1 = new Thread() {@Overridepublic void run() {try {//导致当前线程等到锁存器计数到零,除非线程是 interrupted 。//创建 100 个线程在这里等待latch.await();OneInstanceService one = OneInstanceService.getTest1();if (one.i_am_has_state == 0) {System.out.println("one.i_am_has_state == 0 进程结束");System.exit(0);}//减少锁存器的计数,如果计数达到零,释放所有等待的线程。end.countDown();} catch (InterruptedException e) {e.printStackTrace();}}};t1.start();}//循环完毕,创建结束,减掉计数1,线程被唤醒开始执行latch.countDown();//等待计数为0,也就是100个线程执行完成end.await();//重置OneInstanceService.reset();}}
}

程序在运行时添加 VM 参数 -server 会更容易获得预期的结果,运行后控制台结果如图:

说明 myObject = new myObject() 确实发生了重排序。

更改代码:

static class OneInstanceService{public int i_am_has_state = 0;private volatile static OneInstanceService test;public OneInstanceService() {this.i_am_has_state = new Random().nextInt(200)+1;}public static OneInstanceService getTest1(){if (test == null){synchronized (OneInstanceService.class){if (test == null){test = new OneInstanceService();}}}return test;}public static void reset(){test = null;}}

程序运行后不再打印任何信息,说明禁止重排序后,实例变量 i_am_has_state 永远不是 0 了。也就是说,步骤 A 开辟空间 B 来执行构造方法 C,在赋值代码中插入屏障 ,防止 B 跑到 C 的后面,这样执行顺序永远是 ABC ,而且使用 volatile 还保证了变量的值在多个线程间可见。

3.使用静态内置类实现单例模式 

DCL 可以解决多线程单例模式的非线程安全问题。我们还可以使用其他办法达到同样的效果。 

创建新的测试用例:

public class MyObject {private static class MyobjectHandler {private static MyObject myObject = new MyObject();}private MyObject() {}public static MyObject getInstance() {return MyobjectHandler.myObject;}
}public class MyThread extends Thread{@Overridepublic void run() {System.out.println(MyObject.getInstance().hashCode());}
}public class Run {public static void main(String[] args) {MyThread t1 = new MyThread();MyThread t2 = new MyThread();MyThread t3 = new MyThread();t1.start();t2.start();t3.start();}
}

程序运行后的效果如图:

 

4.序列化和反序列化的单例模式实现 

如果将单例对象进行序列化,使用默认的反序列行为取出对象是多例的。 

创建测试用例:

//实体类代码
public class Userinfo {
}//创建类 MyObject.java
import java.io.ObjectStreamException;
import java.io.Serializable;public class MyObject implements Serializable {private static final long serialVersionUID = 888L;public static Userinfo userinfo = new Userinfo();private static MyObject myObject = new MyObject();private MyObject() {}public static MyObject getInstance() {return myObject;}/*protected Object readResolve() throws ObjectStreamException {System.out.println("调用了 readResolve方法!");return MyObject.myObject;}*/
}

方法  protected Object readResolve() 的作用是反序列化时不创建新的 MyObject 对象,而是复用原有的 MyObject 对象。

创建业务类代码:

import java.io.*;public class SaveAndRead {public static void main(String[] args) {try {MyObject myObject = MyObject.getInstance();System.out.println("序列化 -myObject="+myObject.hashCode()+" userinfo="+myObject.userinfo.hashCode());FileOutputStream fosRef = new FileOutputStream(new File("myObjectFile.txt"));ObjectOutput oosRef = new ObjectOutputStream(fosRef);oosRef.writeObject(myObject);oosRef.close();fosRef.close();} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}try {FileInputStream fisRef = new FileInputStream(new File("myObjectFile.txt"));ObjectInput iosRef = new ObjectInputStream(fisRef);MyObject myObject = (MyObject) iosRef.readObject();iosRef.close();fisRef.close();System.out.println("    序列化 -myObject="+myObject.hashCode()+" userinfo="+myObject.userinfo.hashCode());} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();}}
}

程序运行结果如图:

 

从打印结果可以分析出,在反序列化时创建新的 MyObject 对象,但 Userinfo 对象得到复用,因为 hashcode 是同一个 1163157884 。为了实现 MyObject 在内存中一直呈单例效果,我们可以在反序列化中使用 readResolve() 方法,对原有的 MyObject 对象进行复用:

protected Object readResolve() throws ObjectStreamException {System.out.println("调用了 readResolve方法!");return MyObject.myObject;}

程序运行结果如图:

 

方法 protected Object readResolve() 的作用是在反序列化时不创建新的 MyObject 对象,而是复用 JVM 内存中原有的 MyObject 单例对象,即 Userinfo 对象被复用,这就实现了对 MyObject 序列化与反序列化时保持单例性。

注意:如果将序列化和反序列化操作分别放入两个 class,反序列化时会产生新的 MyObject 对象。放在 2 个 class 类中分别执行其实相当于创建了 2 个 JVM 虚拟机,每个虚拟机里有 1 个 MyObject 对象。我们想要实现的是在 1 个 JVM 虚拟机中进行序列化与反序列化时保持 MyObject 单例性,而不是创建 2 个 JVM 虚拟机。  

补充: 在Java中,对象的序列化是将对象的状态(state)序列化为字节流,而不是重新创建对象。在序列化过程中,对象的引用会被保存下来,而不是对象本身。反序列化时,根据保存的引用创建新的对象,并将序列化的状态恢复到新对象中。  在这段代码中,userinfo对象作为myObject对象的成员变量,被序列化时也一起序列化了。反序列化时,根据之前的引用创建新的myObject对象,并将序列化的userinfo对象的状态恢复到新对象中。因此,userinfo对象并没有被重新创建,而是在序列化和反序列化过程中被复原了状态。 

5.使用 static 静态代码块实现单例模式 

静态代码块中的代码在使用类的时候就已经执行,所以我们可以应用静态代码块的这个特性实现单例模式。 

public class MyObject {private static MyObject instance = null;private MyObject(){}static {instance = new MyObject();}public static MyObject getInstance(){return instance;}
}public class MyThread extends Thread{@Overridepublic void run() {for (int i = 0; i < 5; i++) {System.out.println(MyObject.getInstance().hashCode());}}
}public class Run {public static void main(String[] args) {MyThread t1 = new MyThread();MyThread t2 = new MyThread();MyThread t3 = new MyThread();t1.start();t2.start();t3.start();}
}

运行结果如图:

 

6.使用 enum 枚举类型实现单例模式 

枚举 enum 和静态代码块的特性相似。在使用枚举类时,构造方法会被自动调用。我们也可以应用这个特性实现单例模式。 

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;public enum MyObject {connectionFactory;private Connection connection;private MyObject() {try {System.out.println("调用了 MyObject 的构造器");String url = "jdbc:mysql://localhost:3306/spring_boot";String username = "root";String password = "123456";String driverName = "com.mysql.cj.jdbc.Driver";Class.forName(driverName);connection  = DriverManager.getConnection(url,username,password);} catch (ClassNotFoundException e) {throw new RuntimeException(e);} catch (SQLException e) {e.printStackTrace();}}public Connection getConnection(){return connection;}
}public class MyThread extends Thread{@Overridepublic void run() {for (int i = 0; i <5; i++) {System.out.println(MyObject.connectionFactory.getConnection().hashCode());}}
}public class Run {public static void main(String[] args) {MyThread t1 = new MyThread();MyThread t2 = new MyThread();MyThread t3 = new MyThread();t1.start();t2.start();t3.start();}
}

程序运行结果如图:

 

7.完善使用 enum 枚举类实现单例模式 

修改 MyObject.java 

public class MyObject {public enum MyEnumSingleton{connectionFactory;private Connection connection;private MyEnumSingleton() {try {System.out.println("创建了 MyObject 对象");String url = "jdbc:mysql://localhost:3306/spring_boot";String username = "root";String password = "123456";String driverName = "com.mysql.cj.jdbc.Driver";Class.forName(driverName);connection  = DriverManager.getConnection(url,username,password);} catch (ClassNotFoundException e) {throw new RuntimeException(e);} catch (SQLException e) {e.printStackTrace();}}private Connection getConnection(){return connection;}}public static Connection getConnection(){return MyEnumSingleton.connectionFactory.getConnection();}}

更改 MyThread.java 类的代码:

public class MyThread extends Thread{@Overridepublic void run() {for (int i = 0; i <5; i++) {System.out.println(MyObject.getConnection().hashCode());}}
}

运行结果如图:

  


总结

加油!!!

相关文章:

单例模式与多线程

目录 前言 正文 1.立即加载/饿汉模式 2.延迟加载/懒汉模式 1.延迟加载/懒汉模式解析 2.延迟加载/懒汉模式的缺点 3.延迟加载/懒汉模式的解决方案 &#xff08;1&#xff09;声明 synchronized 关键字 &#xff08;2&#xff09;尝试同步代码块 &#xff08;3&am…...

Kafka系列 - Kafka一篇入门

Kafka是一个分布式流式处理平台。很多分布式处理系统&#xff0c;例如Spark&#xff0c;Flink等都支持与Kafka集成。 Kafka使用场景 消息系统&#xff1a;Kafka实现了消息顺序性保证和回溯消费。存储系统&#xff1a;Kafka把消息持久化到磁盘&#xff0c;相比于其他基于内存的…...

百度 文心一言 sdk 试用

JMaven Central: com.baidu.aip:java-sdk (sonatype.com) Java sdk地址如上&#xff1a; 文心一言开发者 文心一言 (baidu.com) ERNIE Bot SDK https://yiyan.baidu.com/developer/doc#Fllzznonw ERNIE Bot SDK提供便捷易用的接口&#xff0c;可以调用文心一言的能力&#…...

SQLite 和 SQLiteDatabase 的使用

实验七&#xff1a;SQLite 和 SQLiteDatabase 的使用 7.1 实验目的 本次实验的目的是让大家熟悉 Android 中对数据库进行操作的相关的接口、类等。SQLiteDatabase 这个是在 android 中数据库操作使用最频繁的一个类。通过它可以实现数据库的创建或打开、创建表、插入数据、删…...

Dempster-Shafer(D-S)证据理论的基本定义和详细分析,优点,缺点,应用!!(系列1)

文章目录 前言一、D-S证据理论的应用&#xff1a;二、D-S证据理论的优点&#xff1a;三、D-S证据理论的缺陷&#xff1a;四、D-S组合规则&#xff1a;总结 前言 Dempster-Shafer&#xff08;D-S&#xff09;证据理论是一种不精确推理理论&#xff0c;也称为Dempster/Shafer证据…...

Leetcode—15.三数之和【中等】

2023每日刷题&#xff08;四十一&#xff09; Leetcode—15.三数之和 实现代码 class Solution { public:vector<vector<int>> threeSum(vector<int>& nums) {sort(nums.begin(), nums.end());vector<vector<int>> ans;int i, j, k;int s,…...

3、Qt使用windeploy工具打包可执行文件

新建一个文件夹&#xff0c;把要打包的可执行文件exe拷贝过来 点击输入框&#xff0c;复制一下文件夹路径 点击电脑左下角&#xff0c;找到Qt文件夹&#xff0c; 点击打开 “Qt 5.12.0 for Desktop” &#xff08;我安装的是Qt 5.12.0版本&#xff09; 输入“cd bin”&#xff…...

[DFS深度优先搜索]集合里的乘法

集合里的乘法 题目描述 给定一个目标数T和一个整数集合S&#xff0c;判断是否存在S的一个非空子集&#xff0c;子集中的数相乘的积为T。 关于输入 输入为两行。 第一行为目标数T&#xff0c;和S中的元素个数N&#xff0c;以空格隔开。 第二行为S中的N个元素&#xff0c;以空…...

K8s 中 Pod OOMKilled 原因

目录 Exit Code 137 解决方案 JVM 感知 cgroup 限制 使用 JDK9 的容器感知机制尝试 问题分析 容器内部感知 CGroup 资源限制 在 Java10 中&#xff0c;改进了容器集成 JVM 参数 MaxDirectMemorySize -XX:MaxDirectMemorySize 的默认值是什么&#xff1f; 其他获取 ma…...

为什么程序员最应该学习的是运营与销售,而不是技术?

大概几个月前&#xff0c;我加入了某副业交流群。这里人才很多&#xff0c;不光是传统意义上的程序员&#xff0c;也有公司老板、偏门大佬、产品经理等。 群里的聊天主题就是搞钱俩字&#xff0c;大家讨论着如何搞钱&#xff0c;分享每日收益情况&#xff0c;以及自己做的产品等…...

MySql数据库常用指令(五)多表连接

MySql数据库常用指令&#xff08;五&#xff09;多表连接 一、内连接,或等值连接二、左连接三、右连接 实际应用中&#xff0c;我们常常要连接几个不同的MySQL表&#xff0c;因此在 SELECT, UPDATE 和 DELETE 语句中使用 Mysql 的 JOIN 来联合多表查询 INNER JOIN&#xff08;内…...

Centos7使用rpm安装mysql 5.7.43

Centos7使用rpm安装mysql 5.7.43 1、下载rpm包 wget https://downloads.mysql.com/archives/get/p/23/file/mysql-5.7.43-1.el7.x86_64.rpm-bundle.tar2、解压并安装 tar xf mysql-5.7.43-1.el7.x86_64.rpm-bundle.tar yum -y install mysql-*3、按需修改mysql配置 #注意&a…...

补充:如何提高selenium的运行速度?

已经通读该专栏文章的同学,或许对UI自动化测试有了一定的掌握,细心的同学肯定会发现一个问题,当用例量达到一定程度时,对于整体用例的执行速度肯定不会很满意。除了应用多线程运行用例的方式加快速度,有没有其他的方法呢? 今天告诉大家,方法是有的!也是本人新学的。即…...

使用Python+Redis实现文章投票网站后端功能

1&#xff0e;实现投票功能&#xff0c;2&#xff0e;创建文章数据&#xff0c;3&#xff0e;对文章进行排序。 实现投票功能 实现投票功能&#xff0c;要注重文章的时效性与投票的公平性&#xff0c;所以需要给投票功能加上一些约束条件&#xff1a; 文章发布满一个星期后&…...

SpringBoot 环境使用 Redis + AOP + 自定义注解实现接口幂等性

目录 一、前言二、主流实现方案介绍2.1、前端按钮做加载状态限制&#xff08;必备&#xff09;2.2、客户端使用唯一标识符2.3、服务端通过检测请求参数进行幂等校验&#xff08;本文使用&#xff09; 三、代码实现3.1、POM3.2、application.yml3.3、Redis配置类3.4、自定义注解…...

Leetcode—18.四数之和【中等】

2023每日刷题&#xff08;四十一&#xff09; Leetcode—18.四数之和 实现代码 class Solution { public:vector<vector<int>> fourSum(vector<int>& nums, int target) {vector<vector<int>> ans;sort(nums.begin(), nums.end());int n …...

springsecurity6配置二

一、springsecurity6自定义认证异常处理器 1.1 AuthenticationEntryPointImpl.java package com.school.information.core.security.handler;import com.alibaba.fastjson.JSON; import com.school.information.enums.result.ResultStatusEnum; import com.school.informatio…...

php如何对比浮点数大小(bccomp函数)

第一部分&#xff0c;常规例子&#xff1a; 例1&#xff1a;左边比右边小&#xff0c;结果&#xff1a;-1 //示例&#xff0c;左边比右边小返回值&#xff1a;-1 $price1 2.14; $price2 3.14; $result bccomp($price1, $price2, 2); echo 对比结果&#xff1a;.$result;//…...

服务号和订阅号哪个好

服务号和订阅号有什么区别&#xff1f;服务号转为订阅号有哪些作用&#xff1f;在推送频率上来看&#xff0c;服务号每月能推送四条消息&#xff0c;而订阅号可以每天&#xff08;24小时&#xff09;推送一条消息。如果企业开通公众号的目的是提供服务&#xff0c;例如售前资讯…...

面试问题--智能指针

什么是智能指针&#xff1f; 当你在编写程序时&#xff0c;可能需要在运行时动态分配内存来存储数据。在传统的C中&#xff0c;你可能会使用 new 和 delete 操作符来手动管理内存。但是这样容易出现一些问题&#xff0c;比如忘记释放内存导致内存泄漏&#xff0c;或者释放了之…...

[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解

突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 ​安全措施依赖问题​ GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...

conda相比python好处

Conda 作为 Python 的环境和包管理工具&#xff0c;相比原生 Python 生态&#xff08;如 pip 虚拟环境&#xff09;有许多独特优势&#xff0c;尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处&#xff1a; 一、一站式环境管理&#xff1a…...

golang循环变量捕获问题​​

在 Go 语言中&#xff0c;当在循环中启动协程&#xff08;goroutine&#xff09;时&#xff0c;如果在协程闭包中直接引用循环变量&#xff0c;可能会遇到一个常见的陷阱 - ​​循环变量捕获问题​​。让我详细解释一下&#xff1a; 问题背景 看这个代码片段&#xff1a; fo…...

【网络安全产品大调研系列】2. 体验漏洞扫描

前言 2023 年漏洞扫描服务市场规模预计为 3.06&#xff08;十亿美元&#xff09;。漏洞扫描服务市场行业预计将从 2024 年的 3.48&#xff08;十亿美元&#xff09;增长到 2032 年的 9.54&#xff08;十亿美元&#xff09;。预测期内漏洞扫描服务市场 CAGR&#xff08;增长率&…...

Go 语言接口详解

Go 语言接口详解 核心概念 接口定义 在 Go 语言中&#xff0c;接口是一种抽象类型&#xff0c;它定义了一组方法的集合&#xff1a; // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的&#xff1a; // 矩形结构体…...

Spring Boot+Neo4j知识图谱实战:3步搭建智能关系网络!

一、引言 在数据驱动的背景下&#xff0c;知识图谱凭借其高效的信息组织能力&#xff0c;正逐步成为各行业应用的关键技术。本文聚焦 Spring Boot与Neo4j图数据库的技术结合&#xff0c;探讨知识图谱开发的实现细节&#xff0c;帮助读者掌握该技术栈在实际项目中的落地方法。 …...

Unit 1 深度强化学习简介

Deep RL Course ——Unit 1 Introduction 从理论和实践层面深入学习深度强化学习。学会使用知名的深度强化学习库&#xff0c;例如 Stable Baselines3、RL Baselines3 Zoo、Sample Factory 和 CleanRL。在独特的环境中训练智能体&#xff0c;比如 SnowballFight、Huggy the Do…...

使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台

🎯 使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台 📌 项目背景 随着大语言模型(LLM)的广泛应用,开发者常面临多个挑战: 各大模型(OpenAI、Claude、Gemini、Ollama)接口风格不统一;缺乏一个统一平台进行模型调用与测试;本地模型 Ollama 的集成与前…...

Kubernetes 网络模型深度解析:Pod IP 与 Service 的负载均衡机制,Service到底是什么?

Pod IP 的本质与特性 Pod IP 的定位 纯端点地址&#xff1a;Pod IP 是分配给 Pod 网络命名空间的真实 IP 地址&#xff08;如 10.244.1.2&#xff09;无特殊名称&#xff1a;在 Kubernetes 中&#xff0c;它通常被称为 “Pod IP” 或 “容器 IP”生命周期&#xff1a;与 Pod …...

Python竞赛环境搭建全攻略

Python环境搭建竞赛技术文章大纲 竞赛背景与意义 竞赛的目的与价值Python在竞赛中的应用场景环境搭建对竞赛效率的影响 竞赛环境需求分析 常见竞赛类型&#xff08;算法、数据分析、机器学习等&#xff09;不同竞赛对Python版本及库的要求硬件与操作系统的兼容性问题 Pyth…...