欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 教育 > 高考 > 【单例模式】

【单例模式】

2024/10/25 14:30:22 来源:https://blog.csdn.net/C384136844/article/details/142416435  浏览:    关键词:【单例模式】

单例模式是指在内存中只会创建且仅创建一次对象的设计模式。

一、实现方式

1. 饿汉式

 在类加载的时候就创建实例,无论是否使用,实例都会被创建。优点是实现简单,线程安全。缺点是可能造成资源浪费,而程序可能不一定会使用这个实例。

代码示例:

public class Singleton {//创建实例,并且用static保证实例唯一private static final Singleton instance = new Singleton();//把构造方法设置为private,在类外就无法通过new的方式来创建Singleton实例private Singleton() {}//获取实例public static Singleton getInstance() {return instance;}
}


2. 懒汉式写法

public class Singleton {private static Singleton instance = null;private Singleton() {}public static synchronized Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}

说明:在第一次调用获取实例方法时才创建实例。

思考:上述写的饿汉模式懒汉模式,在多线程环境中是否安全呢?为什么?

分析饿汉模式:

饿汉模式,只涉及到读操作,因此,在多线程环境中是线程安全的。

分析懒汉模式:

懒汉模式,涉及到了读和写操作,会出现脏读的情况,因此,懒汉模式不是线程安全的。 

如图所示:

通过加锁改进刚才代码:

public class Singleton {private static Singleton instance = null;private Singleton() {}public static Singleton getInstance() {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}return instance;}
}

说明如下图所示:

刚才代码还有缺陷:分析代码:

public static Singleton getInstance() {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}return instance;}

思考: 

如果每次调用getInstance方法的时候,都会进行加锁操作,加锁操作是有性能开销的,真的需要每次都进行加锁吗?

分析:这里的加锁在new出对象之前加上是有必要的,但是,一旦对象new完了,后续调用getInstance方法,此时instance的值一定是非空的,就会触发return。

解决方案:加上一个条件判断,如果对象没有创建才加锁,否则就return。

代码示例:

public class Singleton {private static volatile Singleton instance;private Singleton() {}public static Singleton getInstance() {//这个条件判断的作用是:是否要加锁if (instance == null) {  synchronized (Singleton.class) {//这个条件判断的作用是:是否要创建对象if (instance == null) {instance = new Singleton();}}}return instance;}
}

你以为就完了吗?

上述代码当中还存在内存可见性和指令重排序的问题。

分析:假设现在有很多线程去调用getInstance方法,只有第一次读的时候才去读内存,后续都是读寄存器或者缓存(此时有如果发生了写操作,其他线程,或者处理器是感知不到的)就会发生内存可见性问题。

指令重排序问题本质上就是编译器或者处理器优化。

instance = new Singleton();这条语句拆分成三个步骤:

1:申请内存空间

2:调用构造方法,把这个内存空间初始化成一个合理的对象

3: 把内存空间的地址赋值给instance引用

 在正常情况下是按照 1 2 3这个顺序去执行,此时此刻,编译器为了提高程序的执行效率,有可能把这个1 2 3 的执行顺序优化成 1 3 2或者其他情况。

如图所示:

 volatile来解决内存可见性和指令重排序问题:

代码示例:

public class Singleton {//volatile来解决内存可见性和指令重排序问题//static实例唯一,共享private static volatile Singleton instance;//禁止外部new对象private Singleton() {}public static Singleton getInstance() {//这个条件判断的作用是:是否要加锁if (instance == null) {  synchronized (Singleton.class) {//这个条件判断的作用是:是否要创建对象if (instance == null) {instance = new Singleton();}}}return instance;}
}


3. 静态内部类方式

利用类加载机制实现延迟加载,只有在调用获取实例的方法时,才会加载静态内部类,从而创建实例。线程安全,并且实现简单高效。

public class Singleton {private Singleton() {}private static class SingletonHolder {private static final Singleton INSTANCE = new Singleton();}public static Singleton getInstance() {return SingletonHolder.INSTANCE;}
}

4. 枚举方式

简洁且线程安全。

public enum SingletonEnum {INSTANCE;public void doSomething() {// 具体方法实现}
}

二、单例模式的优点

1. 减少系统资源开销:避免了频繁创建和销毁对象带来的资源浪费。

2. 保证对象的唯一性:确保在整个应用程序中只有一个实例存在,方便对这个实例进行统一的管理和控制。

3. 方便资源访问:提供了一个全局访问点,可以方便地获取这个唯一的实例,而不需要在多个地方传递实例对象。

三、单例模式的使用场景

1. 日志记录器:通常整个应用程序只需要一个日志记录器实例,以确保所有的日志信息都被记录到同一个地方。

2. 数据库连接池:管理数据库连接,避免频繁地创建和销毁连接,提高性能。

3. 配置文件读取:应用程序通常只需要一个配置文件读取实例,以确保配置信息的一致性。


 

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com