单例模式与多线程

目录

前言

正文

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

运行结果如图:

  


总结

加油!!!

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

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

相关文章

图形编辑器开发:缩放和旋转控制点

大家好&#xff0c;我是前端西瓜哥。好久没写图形编辑器开发的文章了。 今天来讲讲控制点。它是图形编辑器的不可缺少的基础功能。 控制点是吸附在图形上的一些小矩形和圆形点击区域&#xff0c;在控制点上拖拽鼠标&#xff0c;能够实时对被选中进行属性的更新。 比如使用旋…

数据库基础教程之数据库的创建(一)

双击打开Navicat&#xff0c;点击&#xff1a;文件-》新建连接-》PostgreSQL 在下图新建连接中输入各参数&#xff0c;然后点击&#xff1a;连接测试&#xff0c;连接成功后再点击确定。 点击新建数据库 数据库设置如下&#xff1a;

[pyqt5]pyqt5设置窗口背景图片后上面所有图片都会变成和背景图片一样

pyqt5的控件所有都是集成widget&#xff0c;窗体设置背景图片后控件背景也会跟着改变&#xff0c;此时有2个办法。第一个办法显然我们可以换成其他方式设置窗口背景图片&#xff0c;而不是使用styleSheet样式表&#xff0c;网上有很多其他方法。还有个办法就是仍然用styleSheet…

如何申请永久免费的SSL证书

首先&#xff0c;让我们了解什么是SSL证书。 SSL&#xff08;Secure Socket Layer&#xff09;证书是一种数字证书&#xff0c;它提供了一种在互联网上安全地传输数据的方法。 这是一个必须的安全工具&#xff0c;可以加密您的网站和客户之间的所有信息。为了保护用户数据和确保…

Unity 引擎宣布:自 2024 年起,开发者需支付费用!

Unity引擎宣布的新的收费模式&#xff0c;从2024年1月1日开始&#xff0c;根据游戏的安装量来对开发者进行收费。具体来说&#xff0c;每次游戏被下载时&#xff0c;UnityRuntime也会被安装&#xff0c;因此可能会产生额外的费用。对于开发者来说&#xff0c;需要注意以下几点&…

Spring Cloud Gateway 的简单介绍和基本使用

前言 本文主要对Spring Cloud Gateway进行简单的概念介绍&#xff0c;并通过多模块编程的方式进行一个简单的实操。 文章目录 前言1 什么是网关&#xff08;概念&#xff09;2 微服务中的网关2.1 问题12.2 问题2 3 网关作用4 Spring Cloud Gateway组成5 Spring Cloud Gateway基…

tidyverse数据特征学习

目录 特征缩放 1&#xff0c;标准化-scale 2&#xff0c;归一化-rescale 3&#xff0c;行规范化 4&#xff0c;数据平滑 特征变换 1. 非线性特征 2. 正态性变换 3. 连续变量离散 特征降维 特征缩放 不同数值型特征的数据量纲可能相差多个数量级&#xff0c;这对很多…

【企业微信连接问题】

1、个人可以创建企业微信的企业账号么&#xff1f; 答&#xff1a;可以的&#xff0c;只是没法认证。不过基础的功能还是有的。 注册步骤&#xff1a;企业微信注册步骤 2、集简云链接企业微信&#xff0c;在授权之后&#xff0c;找不到集简云怎么办&#xff1f; 答&#xff1a…

计算机组成原理4

1.汇编语言 2.汇编语言常见的运算指令 3.AT&T格式 和 Intel格式 4.跳转指令 5.cmp比较的底层原理 6.函数调用的机器级表示 7.CISC和RISC

多线程详解(未完结)

文章目录 ⭐️写在前面的话⭐️一、线程简介1.1 进程1.2 线程1.3 多线程和多进程的区别1.4 总结 二、继承实现2.1 继承Thread类例子&#xff1a;网图下载 2.2 实现Runnable接口 (推荐)案例&#xff1a;火车站买票问题案例&#xff1a;龟兔赛跑 2.3 实现Callable接口 (了解即可)…

ubuntu20.04打不开github网址的有效解决方案

问题描述&#xff1a;重装的ubuntu系统&#xff0c;chrome浏览器刚开始还能打开github网址&#xff0c;然后突然就打不开了&#xff0c;换网络也不行。 解决方案步骤 1&#xff0c;查询你的电脑IP对应的github网址信息 2&#xff0c;修改host文件&#xff0c;添加第1步查询到…

【React】打包体积分析 source-map-explorer

通过分析打包体积&#xff0c;才能知道项目中的哪部分内容体积过大&#xff0c;方便知道哪些包需要进一步优化。 使用步骤 安装分析打包体积的包&#xff1a;npm i source-map-explorer在 package.json 中的 scripts 标签中&#xff0c;添加分析打包体积的命令对项目打包&…

【C++】多线程(一):std::thread的使用

这篇文章应我朋友的邀请&#xff0c;写一篇文章介绍下C多线程。 编译环境准备 首先确定你的编译器支持std的thread&#xff0c;如果不支持&#xff0c;就会出现诸如“thread找不到”的问题。 以下假设你使用 gnu gcc 编译器&#xff0c;因为 MSVC 的我也不太熟悉。 linux …

Effective Modern C++(1.顶层const与底层const)

1.顶层const与底层const的定义 const修饰的变量不可以改变&#xff0c;那么他就是顶层const&#xff0c;如&#xff1a; const int a 10; 那么&#xff0c;对于 const int *const p new int(10); 第二个const就是顶层const&#xff0c;因为他修饰的是p&#xff1b;第一个…

学习.NET验证模块FluentValidation的基本用法(续3:ASP.NET Core中的调用方式)

FluentValidation模块支持在ASP.NET Core项目中进行手工或自动验证&#xff0c;主要验证方式包括以下三种&#xff1a;   1&#xff09;手工注册验证类&#xff0c;并在控制器或其它模块中调用验证&#xff1b;   2&#xff09;基于ASP.NET验证管道&#xff08;validation …

Visual Studio 中文注释乱码解决方案

在公司多人开发项目中经常遇到拉到最新代码&#xff0c;发现中文注释都是乱码&#xff0c;很是emjoy..... 这是由于编码格式不匹配造成的&#xff0c;如果你的注释是 UTF-8 编码&#xff0c;而文件编码是 GBK 或者其他编码&#xff0c;那么就会出现乱码现象。一般的解决办法是…

打包SpringBoot 项目为本地应用

使用工具&#xff1a;exe4j、Inno Setup Compiler 步骤&#xff1a; 1&#xff0c;将dll包放入项目根路径下&#xff1b; 2&#xff0c;idea 使用Maven打jar包&#xff1b; 3&#xff0c;使用exe4j 工具进行打包&#xff1b; 打开工具首页不动&#xff08;直接 next&#xff…

leetcode_828_统计子串中的唯一字符

题意&#xff1a;所有子串中单个字符出现的次数和 问题转化&#xff1a;对于串中的每个字符&#xff0c;只包含其一次的所有子串的个数和 关于求只包含某位置字符一次的子串个数 class Solution { public:int uniqueLetterString(string s) {/* ...A...A...A...*/int n s.size…

第二十二章 解读pycocotools的API,目标检测mAP的计算COCO的评价指标(工具)

Pycocotools介绍 为使用户更好地使用 COCO数据集, COCO 提供了各种 API。COCO是一个大型的图像数据集&#xff0c;用于目标检测、分割、人的关键点检测、素材分割和标题生成。这个包提供了Matlab、Python和luaapi&#xff0c;这些api有助于在COCO中加载、解析和可视化注释。 …

【Skynet 入门实战练习】实现网关服务 | 用户代理 | RPC 协议 | 客户端

文章目录 前言网关服务RPC 协议看门狗服务代理服务客户端逻辑梳理 前言 上两章学习了如何搭建一个项目&#xff0c;简单实现了几个基础模块。本章节会实现基本的客户端与服务端的通信&#xff0c;包括网关&#xff08;gate&#xff09;、看门狗&#xff08;watchdog&#xff0…