2024年C C++最全【Java设计模式】——单例模式_单例模式java主要作用,2024最新大厂C C++面经

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取


**注意:**


​ **由于枚举方式是饿汉式,因此根据饿汉式的特点,枚举方式也会造成内存浪费,但是在不考虑内存问题下,枚举方式是首选,毕竟实现最简单了**


#### 🍊2.2懒汉式


**懒汉式:类加载时不会创建该单实例对象,首次使用该对象时才会创建**


**🌰1.懒汉式-方式1 (线程不安全)**



public class Demo3 {
/**
*私有构造方法 让外界不能创建该类对象
*/
private Demo3(){}

/\*\*

* 在类中创建该本类对象 static是由于外界获取该类对象的方法getInstance()是 static
* 没有进行赋值(创建对象)
*/
private static Demo3 instance;

/\*\*

* 提供一个公共的访问方式,让外界可以获取该类的对象 static是因为外界不需要创建对象,直接通过类访问
*/
public static Demo3 getInstance(){
//在首次使用该对象时创建,因此instance赋值也就是对象创建 就是在外界获取该单例类的方法getInstance()中创建
instance = new Demo3();
return instance;
}

}



public class Test3 {
public static void main(String[] args) {
Demo3 instance = Demo3.getInstance();

    Demo3 instance1 = Demo3.getInstance();
    //判断两个对象是否是同一个
    System.out.println(instance == instance1);
}

}


输出结果为false,表明我们创建懒汉式单例失败了。是因为我们在调用getInstance()时每次调用都会new一个实例对象,那么也就必然不可能相等了。



// 如果instance为null,表明还没有创建该类的对象,那么就进行创建
if(instance == null){
instance = new Demo3();
}
//如果instance不为null,表明已经创建过该类的对象,根据单例类只能创建一个对象的特点,因此 //我们直接返回instance
return instance;
}


**注意:**


我们在测试是只是单线程,但是在实际应用中必须要考虑到多线程的问题。我们假设一种情况,**线程1进入if判断然后还没来得及创建instance,这个时候线程1失去了cpu的执行权变为阻塞状态,线程2获取cpu执行权,然后进行if判断此时instance还是null,因此线程2为instance赋值创建了该单例对象,那么等到线程1再次获取cpu执行权,也进行了instance赋值创建了该单例对象**,单例模式被破坏。


**🌰2.懒汉式-方式2 (线程安全)**


我们可以通过**加synchronized同步锁的方式保证单例模式在多线程下依旧有效**



public static synchronized Demo3 getInstance(){
//在首次使用该对象时创建,因此instance赋值也就是对象创建 就是在外界获取该单例类的方法getInstance()中创建

    // 如果instance为null,表明还没有创建该类的对象,那么就进行创建

    if(instance == null){
      instance = new Demo3();
    }
    //如果instance不为null,表明已经创建过该类的对象,根据单例类只能创建一个对象的特点,因此我们直接返回instance
    return instance;
}

**注意:**


虽然保证了线程安全问题,**但是在getInstance()方法上添加了synchronized关键字,导致该方法执行效率很低(这是加锁的一个常见问题)**。其实我们可以很容易发现,我们只是在判断instance时需要解决多线程的安全问题,而没必要在getInstance()上加锁


**🌰3.懒汉式-方式3(双重检查锁)**


对于getInstance()方法来说,绝大部分的操作都是读操作,**读操作是线程安全的,没必要让每个线程必须持有锁才能调用该方法,我们可以调整加锁的时机。**



public class Demo4 {
/**
*私有构造方法 让外界不能创建该类对象
*/
private Demo4(){}

/\*\*

*
* 没有进行赋值(创建对象) 只是声明了一个该类的变量
*/
private static Demo4 instance;

/\*\*

* 提供一个公共的访问方式,让外界可以获取该类的对象 static是因为外界不需要创建对象,直接通过类访问
*/
public static Demo4 getInstance(){

    // (第一次判断)如果instance为null,表明还没有创建该类的对象,那么就进行创建
    if(instance == null){
        synchronized (Demo4.class){
            //第二次判断 如果instance不为null
            if(instance == null){
                instance = new Demo4();
            }
        }

    }

    //如果instance不为null,表明已经创建过该单例类的对象,不需要抢占锁,直接返回
    return instance;
}

}


双重检查锁模式完美的解决了单例、性能、线程安全问题,但是只是这样还是有问题的…


 JVM在创建对象时会进行**优化**和**指令重排**,在多线程下可能会发生空指针异常的问题,可以使用volatile关键字,volatile可以保证可见性和有序性。



private static volatile Demo4 instance;


![image-20220322203534148](https://img-blog.csdnimg.cn/img_convert/b23b8fa0eb26f6548c7057870687c7e2.png)


如果发生指令重排 2 和 3 的步骤颠倒,那么instance会指向一块虚无的内存(也有可能是有数据的一块内存)


**完整代码**



public class Demo4 {
/**
*私有构造方法 让外界不能创建该类对象
*/
private Demo4(){}

/\*\*

* volatile可以保证有序性
* 没有进行赋值(创建对象) 只是声明了一个该类的变量
*/
private static volatile Demo4 instance;

/\*\*

* 提供一个公共的访问方式,让外界可以获取该类的对象 static是因为外界不需要创建对象,直接通过类访问
*/
public static Demo4 getInstance(){
// (第一次判断)如果instance为null,表明还没有创建该类的对象,那么就进行创建
if(instance == null){
synchronized (Demo4.class){
//第二次判断 如果instance不为null
if(instance == null){
instance = new Demo4();
}
}
}

    //如果instance不为null,表明已经创建过该单例类的对象,不需要抢占锁,直接返回
    return instance;
}

}


**🌰4.懒汉式-4 (静态内部类)**


静态内部类单例模式中实例由内部类创建,由于JVM在加载外部类的过程中,是不会加载静态内部类的,只有内部类的属性/方法被调用时才会被加载,并初始化其静态属性。静态属性由于被final修饰,保证只被实例化一次,并且严格保证实例化顺序。


**创建单例类**



public class Singleton {

private Singleton(){}

/\*\*

*定义一个静态内部类
*/
private static class SingletonHolder{
//在静态内部类中创建外部类的对象
private static final Singleton INSTANCE = new Singleton();
}

public static Singleton getInstance(){
    return SingletonHolder.INSTANCE;
}

}


**创建测试类**



public class Test4 {
public static void main(String[] args) {
Singleton instance = Singleton.getInstance();

    Singleton instance1 = Singleton.getInstance();
    //判断两个对象是否是同一个
    System.out.println(instance == instance1);
}

}


 **注意:**


​ **第一次加载Singleton类时不会去初始化INSTANCE,只有在调用getInstance()方法时,JVM加载SingletonHolder并初始化INSTANCE,这样可以保证线程安全,并且Singleton类的唯一性**


​  静态内部类单例模式是一种开源项目比较常用的单例模式,在没有任何加锁的情况下保证多线程的安全,并且没有任何性能和空间上的浪费


### 🍉3.单例模式的破坏


单例模式最重要的一个特点就是只能创建一个实例对象,那么如果能使单例类能创建多个就破坏了单例模式(除了枚举方式)破坏单例模式的方式有两种:


#### 🍊3.1序列化和反序列化


从以上创建单例模式的方式中任选一种(除枚举方式),例如**静态内部类方式**



//记得要实现Serializable序列化接口
public class Singleton implements Serializable {

private Singleton(){}

/\*\*

*定义一个静态内部类
*/
private static class SingletonHolder{
//在静态内部类中创建外部类的对象
private static final Singleton INSTANCE = new Singleton();
}

public static Singleton getInstance(){
    return SingletonHolder.INSTANCE;
}

}


**测试类**



public class Test1 {

public static void main(String[] args) throws IOException {
          writeObjectToFile();
}


/\*\*

* 向文件中写数据(对象)
* @throws IOException
*/
public static void writeObjectToFile() throws IOException {
//1.获取singleton对象
Singleton instance = Singleton.getInstance();
//2.创建对象输出流对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(“d:\1.txt”));
//3.写对象
oos.writeObject(instance);
//4.释放资源
oos.close();

}

}


**在d盘根目录下出现一个文件1.txt**由于数据是序列化后的 咱也看不懂


**然后我们从这个文件中读取instance对象**



public static void main(String[] args) throws Exception {
// writeObjectToFile();
readObjectFromFile();
readObjectFromFile();
}
/**
* 从文件中读数据(对象)
* @throws Exception
*/
public static void readObjectFromFile() throws Exception {

    //1.创建对象输入流对象
    ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:\\1.txt"));
    //2.读对象
    Singleton instance = (Singleton) ois.readObject();
    System.out.println(instance);
    //3.释放资源
    ois.close();
}

**输出结果不相同,结论为:序列化破坏了单例模式,两次读的对象不一样了**


`com.xue.demo01.Singleton@2328c243`  
 `com.xue.demo01.Singleton@bebdb06`


**解决方案**


**在singleton中添加readResolve方法**



/**
* 当进行反序列化时,会自动调用该方法,将该方法的返回值直接返回
* @return
*/
public Object readResolve(){
return SingletonHolder.INSTANCE;
}


重新进行写和读,发现两次读的结果是相同的,解决了序列化破坏单例模式的问题


**为什么在singleton单例类中添加readResolve方法就可以解决序列化破坏单例的问题呢,我们在ObjectInputStream源码中在readOrdinaryObject方法中**



private Object readOrdinaryObject(boolean unshared)
throws IOException{
//代码段
Object obj;
try {
//isInstantiable如果一个实现序列化的类在运行时被实例化就返回true
//desc.newInstance()会通过反射调用无参构造创建一个新的对象
obj = desc.isInstantiable() ? desc.newInstance() : null;
} catch (Exception ex) {
throw (IOException) new InvalidClassException(
desc.forClass().getName(),
“unable to create instance”).initCause(ex);
}

//代码段

if (obj != null &&
handles.lookupException(passHandle) == null &&
//hasReadResolveMethod 如果实现序列化接口的类中定义了readResolve方法就返回true
desc.hasReadResolveMethod())
{
//通过反射的方式调用被反序列化类的readResolve方法
Object rep = desc.invokeReadResolve(obj);
if (unshared && rep.getClass().isArray()) {
rep = cloneArray(rep);
}

//代码段

}


#### 🍊3.2反射


从以上创建单例模式的方式中任选一种(除枚举方式),例如**静态内部类方式**


**测试类**


![img](https://img-blog.csdnimg.cn/img_convert/7e5ca487151139289cad8311ed101882.png)
![img](https://img-blog.csdnimg.cn/img_convert/e551976c10c70fa49c0fea4d0ac86588.png)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**

              rep = cloneArray(rep);
            }
       
    //代码段
 }

🍊3.2反射

从以上创建单例模式的方式中任选一种(除枚举方式),例如静态内部类方式

测试类

[外链图片转存中…(img-zHfaL63E-1715534336123)]
[外链图片转存中…(img-cQ7eAHth-1715534336123)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值