Java多线程--解决单例模式中的懒汉式的线程安全问题

文章目录

  • 一、单例设计模式的线程安全问题
    • (1)饿汉式没有线程安全问题
    • (2)懒汉式线程安全问题
      • 1、案例
      • 2、方式1-同步方法
      • 3、方式2-同步代码块
      • 4、优化
  • 二、代码
    • (1)实现线程安全的懒汉式
    • (2)使用内部类

一、单例设计模式的线程安全问题

单例设计模式博客链接:https://blog.csdn.net/m0_55746113/article/details/134492961

  • 饿汉式:不存在线程安全问题。
  • 懒汉式:存在线程安全问题,(需要使用同步机制来处理)

(1)饿汉式没有线程安全问题

饿汉式:在类初始化时就直接创建单例对象,而类初始化过程是没有线程安全问题的。

🍰格式

class Singleton {// 1.私有化构造器private Singleton() {}// 2.内部提供一个当前类的实例// 4.此实例也必须静态化private static Singleton single = new Singleton();// 3.提供公共的静态的方法,返回当前类的对象public static Singleton getInstance() {return single;}
}

【举例】

形式一:

package com.atguigu.single.hungry;public class HungrySingle {private static HungrySingle INSTANCE = new HungrySingle(); //对象是否声明为final 都可以private HungrySingle(){}public static HungrySingle getInstance(){return INSTANCE;}
}

形式二:

/*
public class HungryOne{public static final HungryOne INSTANCE = new HungryOne();private HungryOne(){}
}*/public enum HungryOne{INSTANCE
}

测试类:

package com.atguigu.single.hungry;public class HungrySingleTest {static HungrySingle hs1 = null;static HungrySingle hs2 = null;//演示存在的线程安全问题public static void main(String[] args) {Thread t1 = new Thread() {@Overridepublic void run() {hs1 = HungrySingle.getInstance();}};Thread t2 = new Thread() {@Overridepublic void run() {hs2 = HungrySingle.getInstance();}};t1.start();t2.start();try {t1.join();} catch (InterruptedException e) {e.printStackTrace();}try {t2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(hs1);System.out.println(hs2);System.out.println(hs1 == hs2);//true}}

(2)懒汉式线程安全问题

懒汉式:延迟创建对象,第一次调用getInstance方法再创建对象。

🍰格式

class Singleton {// 1.私有化构造器private Singleton() {}// 2.内部提供一个当前类的实例// 4.此实例也必须静态化private static Singleton single;// 3.提供公共的静态的方法,返回当前类的对象public static Singleton getInstance() {if(single == null) {single = new Singleton();}return single;}
}

1、案例

【案例】

举个例子:

public class BankTest {}class Bank {//私有化构造器private Bank() {     //这里不提供实例变量了}//先不造好,通过一个方法返回private static Bank instance = null;public static Bank getInstance() {if (instance == null) {instance = new Bank();}return instance;}
}

线程安全问题:若有两个线程去调用getInstance方法,他们主要的目的是为了获取instance实例,这个实例就相当于是“共享数据”。

image.png

若第一个线程判断if,发现是null,就进去了。假设此时被sleep阻塞了,然后Bank实例并没有被创建成功。

此时第二个线程也进入了if,判断一下发现是null,然后将Bank实例创建好了。

现在第一个线程阻塞结束,就会再创建一个Bank实例。

所以,instance = new Bank();语句被先后执行了两次,这显然就不是我们想看到的。


🗳️将刚才的问题用代码描述出来。

public class BankTest {static Bank b1=null;   //为了在main方法中调用的时候,不使用对象来调用(方便一点),这里就设置成静态的static Bank b2=null;public static void main(String[] args) {}
}

将b1与b2设置为静态的,就可以直接在main方法中去调用他们了。

在static方法内部只能访问类的static修饰的属性和方法,不能访问类的非static结构。

现在提供两个线程,让他们去调用getInstance方法。

就整一个匿名子类的对象吧:

Thread t1=new Thread(){//重写
};

run方法里面,通过Bank调用getInstance方法,如下:

public class BankTest {static Bank b1=null;   //为了在main方法中调用的时候,不使用对象来调用(方便一点),这里就设置成静态的static Bank b2=null;public static void main(String[] args) {Thread t1=new Thread(){@Overridepublic void run() {b1=Bank.getInstance();	//将方法返回的对象赋给b1}};t1.start();}
}

再创建一个线程t2

public class BankTest {static Bank b1=null;   //为了在main方法中调用的时候,不使用对象来调用(方便一点),这里就设置成静态的static Bank b2=null;public static void main(String[] args) {Thread t1=new Thread(){@Overridepublic void run() {b1=Bank.getInstance(); //将方法返回的对象赋给b1}};Thread t2=new Thread(){@Overridepublic void run() {b2=Bank.getInstance();}};t1.start();t2.start();}
}

线程先后调用getInstance()方法,返回instance方法,地址值依次给了b1和b2。

class Bank {//私有化构造器private Bank() {     //这里不提供实例变量了}private static Bank instance = null;public static Bank getInstance() {if (instance == null) {instance = new Bank();}return instance;}
}

来看看b1与b2的地址值是不是一样的,如下:

public class BankTest {//...System.out.println(b1);System.out.println(b2);System.out.println(b1==b2);}
}

按正常情况来说,两者的地址值应该是相等的,要不然也不能叫单例设计模式了。

但是实际情况也有可能不等,为了让问题突出一点,在这个地方加一个sleep

class Bank {//私有化构造器private Bank() {     //这里不提供实例变量了}private static Bank instance = null;public static Bank getInstance() {if (instance == null) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}instance = new Bank();}return instance;}
}

这样就将问题放大了,看看会不会出现安全问题,如下:

image.png

🎲为什么都是null呢?

两个分线程出去了,主线程还在执行,之所以是null,那是因为主线程先执行了,两个分线程还没有调用getInstance方法,所以就null了。

image.png

有一种方式是在调用start方法的时候,让主线程睡一会,给充分的时间让分线程去执行,但是睡一会也不一定靠谱啊。

最靠谱的就是执行join操作,保证两个分线程执行完之后,主线程才继续向后执行。如下:

t1.start();
t2.start();t1.join();
t2.join();

分别处理一下异常:

t1.start();
t2.start();try {t1.join();
} catch (InterruptedException e) {e.printStackTrace();
}try {t2.join();
} catch (InterruptedException e) {e.printStackTrace();
}

现在这两个分线程执行完之后,主线程才会继续往下去执行。

void join() :等待该线程终止。

在线程a中通过线程b调用join(),意味着线程a进入阻塞状态,直到线程b执行结束,线程a才结束阻塞状态,继续执行。

🌱代码

public class BankTest {static Bank b1=null;   //为了在main方法中调用的时候,不使用对象来调用(方便一点),这里就设置成静态的static Bank b2=null;public static void main(String[] args) {Thread t1=new Thread(){@Overridepublic void run() {b1=Bank.getInstance(); //将方法返回的对象赋给b1}};Thread t2=new Thread(){@Overridepublic void run() {b2=Bank.getInstance();}};t1.start();t2.start();try {t1.join();} catch (InterruptedException e) {e.printStackTrace();}try {t2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(b1);System.out.println(b2);System.out.println(b1==b2);}
}class Bank {//私有化构造器private Bank() {     //这里不提供实例变量了}private static Bank instance = null;public static Bank getInstance() {if (instance == null) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}instance = new Bank();}return instance;}
}

🍺输出结果

image.png

可以看到,两个线程的地址值一样。

是不是巧合呢?

将睡眠时间改为1000ms,如下:

image.png

地址不一样了,这就是线程的不安全性。单例,怎么会有两个呢?

2、方式1-同步方法

现在需要解决线程的不安全问题,主要针对的就是这个方法:

image.png

现在的共享数据是instance,发现操作instance的代码被完全放在getInstance方法里面了。

可以考虑将这个方法声明为同步方法,如下:

image.png

同步方法的同步监视器改不了,是默认的。

此时getInstance方法是默认方法,它的同步监视器是当前类本身。即Bank.class

后面讲反射的时候会提到,Bank.class其实也是个对象,同步监视器一定是由对象来充当的

类加载它,在缓存中只会加载一次,所以是唯一的,所以此时线程安全

🌱代码

package yuyi04.singleton;/*** ClassName: BankTest* Package: yuyi04.singleton* Description:* 实现线程安全的懒汉式** @Author 雨翼轻尘* @Create 2024/1/31 0031 10:39*/
public class BankTest {static Bank b1=null;   //为了在main方法中调用的时候,不使用对象来调用(方便一点),这里就设置成静态的static Bank b2=null;public static void main(String[] args) {Thread t1=new Thread(){@Overridepublic void run() {b1=Bank.getInstance(); //将方法返回的对象赋给b1}};Thread t2=new Thread(){@Overridepublic void run() {b2=Bank.getInstance();}};t1.start();t2.start();try {t1.join();} catch (InterruptedException e) {e.printStackTrace();}try {t2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(b1);System.out.println(b2);System.out.println(b1==b2);}
}class Bank {//私有化构造器private Bank() {     //这里不提供实例变量了}private static Bank instance = null;public static synchronized Bank getInstance() { //同步监视器是Bank.classif (instance == null) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}instance = new Bank();}return instance;}
}

🍺输出结果

image.png

此时不管睡眠多久,得到的地址值一定是一样的。

可以看到,之前睡100s的时候,有执行正确的时候。

也就是虽然执行结果正确,但是存在出现错误的可能性。只是出现概率比较低,不一定能发现问题。

这就需要经验,你要知道这里边会出现相关问题,要提前预防这样的问题。

3、方式2-同步代码块

刚才方式1是把这个方法声明为同步方法了,如下:

image.png

其实我们还可以使用同步代码块

就是将他们包裹起来,如下:

image.png

快捷键Ctrl+Alt+T,选择第9个:

image.png

那么小括号里面些什么呢?要保证唯一性。

不可以写this,因为此时是静态方法。

所以还是这样来写Bank.class,如下:

//实现线程安全的方式2
public static  Bank getInstance() { //同步监视器是Bank.classsynchronized (Bank.class) {if (instance == null) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}instance = new Bank();}return instance;}
}

这里和方法1没有本质区别,就是将代码主动用synchronized包裹了一下而已。

有一个小细节,这里将return instance;放在外面能稍微好一点。

//实现线程安全的方式2
public static  Bank getInstance() { //同步监视器是Bank.classsynchronized (Bank.class) {if (instance == null) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}instance = new Bank();}}return instance;
}

因为返回instance并没有修改instance的值,所以线程先后执行这条语句不影响结果。

🌱代码

public class BankTest {static Bank b1=null;   //为了在main方法中调用的时候,不使用对象来调用(方便一点),这里就设置成静态的static Bank b2=null;public static void main(String[] args) {Thread t1=new Thread(){@Overridepublic void run() {b1=Bank.getInstance(); //将方法返回的对象赋给b1}};Thread t2=new Thread(){@Overridepublic void run() {b2=Bank.getInstance();}};t1.start();t2.start();try {t1.join();} catch (InterruptedException e) {e.printStackTrace();}try {t2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(b1);System.out.println(b2);System.out.println(b1==b2);}
}class Bank {//私有化构造器private Bank() {     //这里不提供实例变量了}private static Bank instance = null;//实现线程安全的方式2public static  Bank getInstance() { //同步监视器是Bank.classsynchronized (Bank.class) {if (instance == null) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}instance = new Bank();}}return instance;}
}

🍺输出结果

image.png

4、优化

观察一下下面的代码:

public static  Bank getInstance() { //同步监视器是Bank.classsynchronized (Bank.class) {if (instance == null) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}instance = new Bank();}}return instance;
}

多线程的“同步机制”:一个线程握着同步监视器,进入了同步代码块里面操作,后面的线程必须在第一个线程执行结束释放同步监视器之后才能进入同步代码块

现在来想个问题,第一个线程进入同步代码块之后,创建了instance,然后执行结束释放了同步监视器。

对于后面的线程来说,他们也不需要再去new Bank()了,因为此时instance不是null,后续线程进入同步代码块里面什么也不需要执行就出来了。

所以就希望后续线程调用同步代码块的时候,直接拿着已经创建好的instance走即可,不必要一直等着进去然后啥也没干就出来了。

现在就在方法2的基础之上稍微优化一下。

//实现线程安全的方式3
public static  Bank getInstance() { //同步监视器是Bank.classif (instance == null) {synchronized (Bank.class) {if (instance == null) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}instance = new Bank();}}}return instance;
}

🎲现在在外面加了if,那么里面还需要再加吗?

需要的。

🍰分析

比如此时线程1执行getInstance方法,碰到了最外层的if判断,此时instance是null,然后它就进入这个if,接下来碰到了同步代码块,拿到锁并进入同步代码块。

接下来线程1执行sleep操作,也就是instance还没有被创建,也就意味着此时instance判断还是null。

若此时线程2也执行了getInstance方法,判断最外层的if,此时线程1还在sleep,没有创建instance,所以线程2判断instance是null,进入最外层的if,接下来碰到了同步代码块,但是锁被线程1拿走了,就只能在同步代码块外面等着。

直到线程1sleep结束,然后创建了instance,出了同步代码块,释放锁,线程2才能拿到锁进入同步代码块。

此时碰到了内层if,判断得知instance不是null了,因为线程1已经创建好了instance。

所以线程2就直接出了同步代码块。

若此时线程3也执行getInstance方法,判断最外层的if,因为线程1已经创建了instance,所以它就不用继续往下执行了,直接略过if,返回instance即可。

后面的线程就都不需要进入if了,直接返回instance即可。

这样就保证了instance的唯一。

如下:

image.png

后续的线程只要在instance被实例化以后,就不会再进入同步代码块了。

优化之后的代码效率会更高一些。


若是将内层的if删除

假设两个线程都进入if()后, 线程1先进入同步监视器,创建了一个新Bank ,线程1结束执行后,由于在同步监视器之前就已经判断好instance为nulll,那么线程2也会进入创建一个新对象,所以不安全。如下:

public static  Bank getInstance() { //同步监视器是Bank.classif (instance == null) {synchronized (Bank.class) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}instance = new Bank();}}return instance;
}

优化之后,相当于在实例创建完后,代码的执行就变成并行的而不是串行的了,线程之间不用再争夺锁,相当于繁琐了实例创建前的步骤而简化了实例创建后的步骤。

🌱代码

public class BankTest {static Bank b1=null;   //为了在main方法中调用的时候,不使用对象来调用(方便一点),这里就设置成静态的static Bank b2=null;public static void main(String[] args) {Thread t1=new Thread(){@Overridepublic void run() {b1=Bank.getInstance(); //将方法返回的对象赋给b1}};Thread t2=new Thread(){@Overridepublic void run() {b2=Bank.getInstance();}};t1.start();t2.start();try {t1.join();} catch (InterruptedException e) {e.printStackTrace();}try {t2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(b1);System.out.println(b2);System.out.println(b1==b2);}
}class Bank {//私有化构造器private Bank() {     //这里不提供实例变量了}private static Bank instance = null;//实现线程安全的方式3:相较于方式1和方式2来说,效率更高public static  Bank getInstance() { //同步监视器是Bank.classif (instance == null) {synchronized (Bank.class) {if (instance == null) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}instance = new Bank();}}}return instance;}
}

🍺输出结果

image.png


☕注意:上述方式3中,有指令重排问题。

比如线程1进入if,判断instance是null,然后拿到同步锁,进入同步代码块,碰到了sleep睡了。

此时instance还没有创建,所以线程2可以进入最外层的if,但是进不去同步代码块。

然后线程1sleep结束后,执行instance=new Bank;的操作。此时对象已经创建了(instance已经不是null了),但是有可能还没有执行以构造器为代表的init方法,也就是对象已经有了,但是还没有初始化完成,没有真正执行完所有的步骤。(初始化分为好多步骤,有一个环节就是已经创建好了对象)

因为有指令重排,所以在没有初始化完成的时候,线程1它有出去的可能,虽然没有完全执行完init方法,但是对象已经有了。

若此时线程2拿到了锁进入同步代码块,发现instance不是null,就会直接略过内层if,直接return了现在的instance,但此时的instance还没有初始化完成,就会有风险。

mem = allocate(); 为单例对象分配内存空间
instance = mem;   instance引用现在非空,但还未初始化
ctorSingleton(instance); 为单例对象通过instance调用构造器

从JDK2开始,分配空间、初始化、调用构造器(对象创建的过程)会在线程的工作存储区一次性完成,然后复制到主存储区。
但是需要volatile关键字修饰,避免指令重排

🚗解决方案

instance前面加上关键字volatile即可避免重排问题。

如下:

image.png

代码:

class Bank {//私有化构造器private Bank() {     //这里不提供实例变量了}private static volatile Bank instance = null;//实现线程安全的方式3:相较于方式1和方式2来说,效率更高;为了避免指令重排,需要将instance声明为volatilepublic static  Bank getInstance() { //同步监视器是Bank.classif (instance == null) {synchronized (Bank.class) {if (instance == null) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}instance = new Bank();}}}return instance;}
}

创建对象过程:1.分配内存空间 2.初始化对象 3.引用指向刚分配的内存空间,JDK2.0起,为了优化,调整顺序1->3->2,即指令重排,这带来隐患,引用不为null但还没初始化对象。

二、代码

(1)实现线程安全的懒汉式

package yuyi04.singleton;/*** ClassName: BankTest* Package: yuyi04.singleton* Description:* 实现线程安全的懒汉式** @Author 雨翼轻尘* @Create 2024/1/31 0031 10:39*/
public class BankTest {static Bank b1=null;   //为了在main方法中调用的时候,不使用对象来调用(方便一点),这里就设置成静态的static Bank b2=null;public static void main(String[] args) {Thread t1=new Thread(){@Overridepublic void run() {b1=Bank.getInstance(); //将方法返回的对象赋给b1}};Thread t2=new Thread(){@Overridepublic void run() {b2=Bank.getInstance();}};t1.start();t2.start();try {t1.join();} catch (InterruptedException e) {e.printStackTrace();}try {t2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(b1);System.out.println(b2);System.out.println(b1==b2);}
}class Bank {//私有化构造器private Bank() {     //这里不提供实例变量了}private static volatile Bank instance = null;//实现线程安全的方式1/*public static synchronized Bank getInstance() { //同步监视器是Bank.classif (instance == null) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}instance = new Bank();}return instance;}*///实现线程安全的方式2/*public static  Bank getInstance() { //同步监视器是Bank.classsynchronized (Bank.class) {if (instance == null) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}instance = new Bank();}}return instance;}*///实现线程安全的方式3:相较于方式1和方式2来说,效率更高;为了避免指令重排,需要将instance声明为volatilepublic static  Bank getInstance() { //同步监视器是Bank.classif (instance == null) {synchronized (Bank.class) {if (instance == null) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}instance = new Bank();}}}return instance;}
}

(2)使用内部类

package com.atguigu.single.lazy;public class LazySingle {private LazySingle(){}public static LazySingle getInstance(){return Inner.INSTANCE;}private static class Inner{static final LazySingle INSTANCE = new LazySingle();}}

内部类只有在外部类被调用才加载,产生INSTANCE实例;又不用加锁。

此模式具有之前两个模式的优点,同时屏蔽了它们的缺点,是最好的单例模式。

此时的内部类,使用enum进行定义,也是可以的。

测试类:

package com.atguigu.single.lazy;import org.junit.Test;public class TestLazy {@Testpublic void test01(){LazyOne s1 = LazyOne.getInstance();LazyOne s2 = LazyOne.getInstance();System.out.println(s1);System.out.println(s2);System.out.println(s1 == s2);}//把s1和s2声明在外面,是想要在线程的匿名内部类中为s1和s2赋值LazyOne s1;LazyOne s2;@Testpublic void test02(){Thread t1 = new Thread(){public void run(){s1 = LazyOne.getInstance();}};Thread t2 = new Thread(){public void run(){s2 = LazyOne.getInstance();}};t1.start();t2.start();try {t1.join();t2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(s1);System.out.println(s2);System.out.println(s1 == s2);}LazySingle obj1;LazySingle obj2;@Testpublic void test03(){Thread t1 = new Thread(){public void run(){obj1 = LazySingle.getInstance();}};Thread t2 = new Thread(){public void run(){obj2 = LazySingle.getInstance();}};t1.start();t2.start();try {t1.join();t2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(obj1);System.out.println(obj2);System.out.println(obj1 == obj2);}
}

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

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

相关文章

猫什么时候发腮?公认发腮效果好的生骨肉冻干推荐

猫什么时候发腮是许多猫主人非常关心的问题。在猫咪的成长过程中,发腮是一项重要的体征,也是猫咪成熟的标志。想要让猫咪拥有可爱的肉嘟嘟脸型,主人需要在适龄的年龄段加强营养补给,不要错失最佳发腮期。那么,猫咪的最…

api接口1688商品详情接口采集商品详情数据商品价格详情页数据可支持高并发调用演示示例

接入1688商品详情API接口的步骤如下: 注册账号:首先,你需要在1688开放平台注册一个账号。 创建应用:登录后,在控制台中找到“我的应用”,点击“创建应用”。 获取API密钥:创建应用后&#xff…

【Linux】VMware Workstation16安装银河麒麟高级服务器操作系统V10 SP3 AMD64

目录 一、麒麟服务器概述 二、安装步骤 设置硬盘大小 完成配置 修改内存 处理器等设备配置 选择直接安装 配置磁盘 网络配置 设置root账号密码 开始安装 启动完成 一、麒麟服务器概述 银河麒麟高级服务器操作系统V10是针对企业级关键业务,适应虚拟化、云…

时间序列预测——GRU模型

时间序列预测——GRU模型 在深度学习领域,循环神经网络(RNN)是处理时间序列数据的一种常见选择。上期已介绍了LSTM的单步和多步预测。本文将深入介绍一种LSTM变体——门控循环单元(GRU)模型,包括其理论基础…

Flutter canvas 画一条会动的波浪线 进度条

之前用 Flutter Canvas 画过一个三角三角形,html 的 Canvas 也画过一次类似的, 今天用 Flutter Canvas 试了下 感觉差不多: html 版本 大致效果如下: 思路和 html 实现的类似: 也就是找出点的位置,使用二阶…

Cannot assign to read only property ‘exports‘ of object ‘#<Object>‘

看下多语言js文件中,是否同级出现相同名称。

3D 转换

1,3D的特点: 近小远大 物体后面遮挡不可见 2,3D移动 translate3d 3D移动在2D移动的基础上多加了一个可以移动的方向,就是z轴方向 transform:translateX(100px):仅仅是在x轴上移动…

【C/C++ 07】词频统计

一、题目 读入一篇英文短文&#xff0c;去除介词、连词、冠词、副词、代词等非关键性单词后&#xff0c;统计每个单词出现的次数&#xff0c;并将单词按出现次数的降序和单词字符的升序进行显示5个单词。 二、算法 1. 通过<fstream>库创建fstream流对象&#xff0c;并从…

美区或其他外区Appstore账号AppleID注册教程,简单快速,苹果必备!

▍前言 现在越来越多的APP在国区APPstore下架&#xff0c;如果想有更好的使用体验&#xff0c;不得不去外区下载APP&#xff0c;那就需要一个外区的apple id&#xff0c;注册也很简单&#xff0c;今天大鹏通过电脑ipad给大家注册一个&#xff0c;建议大家直接使用iPhone或者iPa…

好书推荐丨保姆级Midjourney教程,这本写给大家看的设计书闭眼入!

文章目录 写在前面好书推荐Part.1Part.2Part.3 粉丝福利写在后面 写在前面 在AI绘画界&#xff0c;有每日经典一问&#xff1a;“你今天用Midjourney画了啥&#xff1f;”晒作品成为重头戏。 小红书上关于Midjourney出的图片点赞数惊人。 reddit上的恶搞幽默图片热度居高不下…

GSM-TRIAL-21.04.9-VMware-Workstation.OVA安装教程,GreenBone虚拟机安装教程

将GSM-TRIAL-21.04.9-VMware-Workstation.ova用VMware打开 先设置好网络和内存&#xff1a; 1、打开虚拟机&#xff0c;显示&#xff1a;你的GSM还不能完全正常工作。您想现在完成设置吗? 点击yes 2、创建用户&#xff0c;一会儿登录网页要用&#xff0c;点击yes 3、创建用户…

指向 Data Member 的指针

看一下很简单的一个例子&#xff1a; #include <stdlib.h> #include <stdio.h> #include <malloc.h>class origin { public:virtual ~origin(){} public:int x; };int main() {origin A;printf("&origin::x %p, &A.x %p\n", &origi…

小黄鸭聊电脑(4)硬盘分区

小黄鸭聊电脑(4)硬盘分区 夜深人静&#xff0c;万籁俱寂&#xff0c;老郭趴在电脑桌上打盹&#xff0c;桌子上的小黄鸭和桌子旁的冰箱又开始窃窃私语…… 小黄鸭&#xff1a;冰箱大哥&#xff0c;上次你说的那个“分区”和“格式化”是什么意思&#xff1f; 冰箱&#xff1a;…

洛夫克拉夫特与文学中的超自然恐怖:前哥特时代

洛夫克拉夫特与文学中的超自然恐怖&#xff1a;前哥特时代 ![ 洛夫克拉夫特是美国恐怖、科幻与奇幻小说作家&#xff0c;尤以其怪奇小说著称&#xff0c;他在自己的一系列小说中开发出了克苏鲁神话体系。他的创作对后世恐怖小说创造影响深远&#xff0c;我们可以在许多当代文…

[leetcode] 21. 合并两个有序链表

文章目录 题目描述解题方法双指针遍历java代码 题目描述 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1&#xff1a; 输入&#xff1a;l1 [1,2,4], l2 [1,3,4] 输出&#xff1a;[1,1,2,3,4,4]示例 2&#xff…

自动驾驶:Apollo如何塑造人类的未来出行

前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家&#xff1a;https://www.captainbed.cn/z ChatGPT体验地址 文章目录 前言1. 什么是自定义指令&#xff1f;2. Apollo中的自定义指令2.1 查询中的自定…

鸿蒙HarmonyOS——AVSession开发指导

AVSession开发指导 说明&#xff1a; AVSession的所有接口均为系统接口&#xff0c;其功能仅提供给系统应用使用。 会话接入端开发指导 基本概念 会话元数据AVMetadata: 媒体数据相关属性&#xff0c;包含标识当前媒体的ID(assetId)&#xff0c;上一首媒体的ID(previousAsset…

17. Spring Boot Actuator

17. Spring Boot Actuator Spring Boot执行器(Actuator)提供安全端点&#xff0c;用于监视和管理Spring Boot应用程序。 默认情况下&#xff0c;所有执行器端点都是安全的。 在本章中&#xff0c;将详细了解如何为应用程序启用Spring Boot执行器。 启用Spring Boot Actuator …

光纤熔接-热熔

实验教学日的及具体要求 目的 1.掌握室外光缆、皮线光缆的开剥方法。 2.掌握应用光纤切割刀制作光纤端面的方法 3.掌握光纤熔接的基本知识。 4.掌握光纤熔接机的使用方法及接续步骤。 任务 1.完成2芯光缆在终端盒内与尾纤的熔接 2.用激光笔完成光纤熔接检测。 …

如何使用mock.js实现接口测试的自动化?

Mock.js 基础用法介绍 Mock.js是一个常用于生成随机数据和拦截Ajax请求的JavaScript库。本文将介绍Mock.js的用法&#xff0c;包括安装和基础用法&#xff0c;在开始前我们可以看下看&#xff1a;了解 Mock.js 的语法规范。 安装 可以通过npm安装Mock.js&#xff1a; npm i…