一、单例设计模式
就是一个类只允许创建一个对象,那么我们称该类为单例类,这种设计模式我们称为单例模式。
二、为什么使用单例模式
资源共享:有些类拥有共享的资源,例如数据库连接池、线程池、缓存等。使用单例模式确保只有一个实例,避免资源浪费和竞争条件。
线程安全:单例模式可以用来保证多线程环境下只有一个实例,从而避免竞争条件和不一致性。
避免全局变量:使用单例模式可以减少全局变量的数量,从而降低了不同部分之间的耦合。这有助于使代码更加可维护和可测试。
全局访问点:单例模式提供了一个全局的访问点,使得其他对象可以轻松地获取该实例,而无需创建多个实例或传递实例引用。这对于配置管理、日志记录、事件管理等非常有用。
三、如何实现单例
常见的单例设计模式有以下五种:
1、饿汉式
2、懒汉式
3、双重检查锁
4、静态内部类
5、枚举
我们在编写以上五种单例代码的时候需要注意以下几点:
1、构造器私有化
2、暴露一个公共方法获取单例对象
3、是否考虑线程安全
4、是否考虑加载时间
对于1、2两点来说,我常常是这样想的,一个单例类要有两个private、两个static。构造器和对象必须用private修饰,对象和获取对象的方法必须用static修饰。
注:1、将对象定义为static的主要原因有两个,其一是确保只有一个实例可以被全局访问。static成员变量是属于类而不是实例的,这意味着不管创建多少个类的实例,它们都共享相同的static成员变量。这对于单例模式非常重要,因为单例的核心目标是确保只有一个实例存在。其二是返回对象的方法是静态的,静态方法不能访问非静态成员变量。
我们先来看第一个单例模式的代码
四、饿汉式
饿汉式的实现方式比较简单。具体的代码实现如下:
public class EagerSingleton {
private final static EagerSingleton instance = new EagerSingleton();
private EagerSingleton() {}
public static EagerSingleton getInstance(){
return instance;
}
}
从上述代码可以看出,在类加载的时候,instance 静态实例就已经创建并初始化好了,所以,instance 实例的创建过程是线程安全的。如果不需要延迟加载,并且确保在多线程环境下只有一个实例存在,饿汉式单例是一种有效的选择。
五、懒汉式
有饿汉式,对应地,就有懒汉式。懒汉式相对于饿汉式的优势是支持延迟加载,具体的代码实现如下:
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {}
public static LazySingleton getInstance(){
if(instance == null){
instance = new LazySingleton();
}
return instance;
}
}
以上的写法本质上是有问题,在多线程环境下,其实是无法保证其单例的特点的,很有可能会有超过一个线程同时执行了new Singleton();解决的办法简单粗暴,直接加锁,代码如下:
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {}
public static synchronized LazySingleton getInstance(){
if(instance == null){
instance = new LazySingleton();
}
return instance;
}
}
以上的写法确实可以保证jvm中有且仅有一个单例实例存在,但是方法上加锁会极大 的降低获取单例对象的并发度。同一时间只有一个线程可以获取单例对象,为了解决 以上的方案则有了第三种写法。
六、双重检查锁
饿汉式不支持延迟加载,懒汉式有性能问题,不支持高并发。那我们再来看一种既支 持延迟加载、又支持高并发的单例实现方式,也就是双重检测实现方式(办法总比困难多嘛):在这种实现方式中,只要 instance 被创建之后,即便再调用 getInstance() 函数也不会再进入到加锁逻辑中了。所以,这种实现方式解决了懒汉式并发度低的问题。具体的代码实现如下:
public class DoubleCheckSingleton {
private volatile static DoubleCheckSingleton instance;
private DoubleCheckSingleton() {}
public static DoubleCheckSingleton getInstance(){
if(instance == null){
synchronized (DoubleCheckSingleton.class){
if(instance == null){
instance = new DoubleCheckSingleton();
}
}
}
return instance;
}
}
volatile: 加volatile关键字是为了保证顺序性,因为在Java中创建对象通常需要三个主要步骤,分别为分配内存空间、初始化对象、引用赋值,正是因为创建需要这三步,导致在多线程环境下,出现某些线程拿到的对象是半初始化对象。譬如:
线程1进入双重检测锁,执行第一次检测,发现共享的实例变量为null,因此进入同步块。
线程1在同步块内部创建了单例对象,然后将实例变量指向新创建的对象。
由于指令重排序,创建对象的操作被移到了实例变量赋值之前。这样,实例变量被赋值,但对象可能还没有完全初始化。
线程2在第一次检测时,发现实例变量不为null,然后直接返回实例,但实例可能没有完全初始化,导致不完整或不正确的对象。
七、静态内部类
我们再来看一种比双重检测更加简单的实现方法,那就是利用 Java 的静态内部类。 它有点类似饿汉式,但又能做到了延迟加载。具体是怎么做到的呢?我们先来看它的代码实现如下:
public class InnerSingleton {
private InnerSingleton(){}
public static InnerSingleton getInstance(){
return InnerSingletonHolder.instance;
}
// 静态内部类实例化外部类对象
private static class InnerSingletonHolder{
private final static InnerSingleton instance = new InnerSingleton();
}
}
InnerSingletonHolder 是一个静态内部类,当外部类 InnerSingleton被加载的时候,并不会创建 InnerSingletonHolder 实例对象。只有当调用 getInstance() 方法时,
InnerSingletonHolder 才会被加载,这个时候才会创建 instance。insance 的唯一性、创建过程的线程安全性,都由 JVM 来保证。所以,这种实现方法既保证了线程安全,又能做到延迟加载。
八、枚举
基于枚举类型的单例实现。这种实现方式通过 Java 枚举类型本身的特性,保证了实例创建的线程安全性和实例的唯一性。具体的代码如下:
public enum EnumSingleton {
// 对应的枚举类,任何一个枚举项都是一个天然的单例
// 本质上就是static final EnumSingleton instance = new EnumSingleton()
// 枚举本身支持懒加载
INSTANCE;
}
更通用的写法如下:
public class EnumSingleton {
private EnumSingleton(){}
public static enum SinglentonEnum{
ESingleton;
private EnumSingleton instance = null;
private SinglentonEnum(){
instance = new EnumSingleton();
}
public EnumSingleton getInstance(){
return instance;
}
}
}
通过调用 EnumSingleton.SinglentonEnum.ESingleton.getInstance() 方法可以获取单例对象。
单例模式就分享到这,希望可以帮助各位码友!点个赞吧!!!
文章来源地址https://www.toymoban.com/news/detail-724163.html
文章来源:https://www.toymoban.com/news/detail-724163.html
到了这里,关于设计模式之单例设计模式的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!