单例模式之常见模式详解
- 单例模式的定义
- 单例模式的分类
- 饿汉模式
- 懒汉模式
- 单例模式的主要特点
- 单例模式的应用场景
- 总结
单例模式的定义
单例模式是一种设计模式,用于确保一个类只有一个实例,并提供一个全局访问点来获取该实例。
在单例模式中,类的构造函数被私有化,这样其他类就无法直接创建该类的实例。而是通过一个静态方法或者属性来获取类的唯一实例。
单例模式的分类
在Java中实现单例模式的方法有很多,这里介绍最常见的两种
饿汉模式
以下是具体实现:
public class Singleton {
//此处,先把实例创建出来
private static Singleton instance=new Singleton();
//如果需要使用这个唯一实例,统一通过 Singleton.getInstance()方法来获取。
public static Singleton getInstance(){
return instance;
}
//为了避免Singleton 类不小心被复制出多份来
//把构造方法设置为private.在类外面,就无法通过new的方式来创建这个Singleton实例
private Singleton(){
}
}
此处static起着至关重要的作用:
- static 保证这个实例唯一
- static 保证这个实例确实在一定时机中被创建出来
static 修饰使得当前 instance 属性是类属性,类属性是在类对象上的,类对象在一个java进程里是唯一的(只是在类加载阶段被创建出一个实例)
补充知识点:
类加载:
java代码中的每个类都会在编译完成后得到 .class 文件。JVM运行时会加载这个 .class文件,读取其中的二进制指令,并且在内存中构造出对应的类对象。(形如Singleton.class)
- 把构造方法设为private,在类外面,就无法通过new的方式来创建这个实例了。
饿汉模式的优点是实现简单、线程安全,因为实例在类加载时就已经创建好了。但缺点是如果该实例在整个程序运行期间没有被使用到,会造成资源浪费。
懒汉模式
以下是具体实现:
public class SingletonLazy {
private static SingletonLazy instance=null;
public static SingletonLazy getInstance(){
if(instance==null){
instance=new SingletonLazy();
}
return instance;
}
private SingletonLazy(){
}
}
懒汉模式没有在类加载时进行初始化,而是在第一次使用该类的实例时,通过判断实例是否已经存在来决定是否创建实例。(延迟实例化,在需要时才创建唯一的实例。)
懒汉模式的优点是实现简单,只有在需要时才创建实例,避免了资源浪费。但缺点是在多线程环境下可能会出现线程安全问题,需要额外处理。
如何让上述懒汉模式能够线程安全呢?进行加锁操作
由于使用了synchronized关键字,可能会影响性能。为了提高性能,可以使用双重检查锁定等方式进行优化。
上述代码会导致每次 getInstance() 操作都要加锁,加锁操作是有开销的,然而如果判断instance 的值是非空时,就会直接触发return操作,此时由于没有修改操作,就没必要加锁。
如下:
为了避免上述代码可能会遇到的内存可见性问题和指令重排序问题,使用volatile关键字进行优化。
代码如下:
public class SingletonLazy {
private volatile static SingletonLazy instance=null;
public static SingletonLazy getInstance(){
if (instance == null) {
synchronized (SingletonLazy.class) {
if (instance == null) {
instance = new SingletonLazy();
}
}
}
return instance;
}
private SingletonLazy(){
}
}
单例模式的主要特点
- 私有的构造函数:通过将构造函数设为私有,防止其他类直接创建实例。
- 静态的实例变量:在类内部定义一个静态的实例变量,用于保存类的唯一实例。
- 静态的获取方法:提供一个静态的方法或属性,用于获取类的唯一实例。这个方法会在第一次调用时创建实例,并在后续调用时返回同一个实例。
单例模式的应用场景
- 系统中某个类的对象只需要存在一个实例,例如配置信息类、日志记录类等。
- 需要频繁创建和销毁对象的场景,通过使用单例模式可以节省系统资源。
- 对象需要被共享或者全局访问的场景,例如线程池、数据库连接池等。
总结
在实现单例模式时,需要注意线程安全性和延迟加载的问题。可以使用加锁、双重检查锁定等方式来确保线程安全,并且在需要时才创建实例,避免资源浪费。