单例模式的定义:确保一个类只有一个实例,并提供一个全局访问点。
刚接触单例模式,我们可能有以下问题:我们为什么需要单例模式?什么场景下需要单例模式?单例模式的好处是什么?通过静态变量实现单例不行吗?
什么场景下需要单例模式?有些对象在应用中只需要一个,比如线程池、缓存、对话框、注册表、日志和设备驱动对象等,这些对象在使用时往往只能存在一个,不然如果多个实例在程序中运行可能会出现行为异常和资源使用过量等问题。
通过静态变量实现单例不行吗?单例模式的好处是什么?
好处之一就是延迟初始化。当使用静态变量时创建实例时,会在加载类的同时进行变量的初始化,如果变量的初始化过程庞大且耗时,即使没有使用变量也会耗费大量的等待时间。而单例模式是能延迟初始化过程的,可以在实际使用时在进行初始化,使对象创建更为方便。
这就是我们需要单例模式的理由之一,当然,单例模式还有其他好处,如单例模式还能设置全局访问点,这样能更好的管控单例对象,保证对象使用的安全性等。
如何实现单例模式?
第一种实现单例模式的代码如下,通过静态变量将实例保存在类中:
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {}
public static Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
上面的单例模式貌似没有什么问题,但是在多线程中,可能会出现多个线程同时创建单例的情况,这时候以上代码没有多线程管控,可能会出现创建多个实例的情况。
所以我们可能需要考虑在单例创建过程中的多线程同步问题,重新增加了方法同步:
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
如果单例获取不是很频繁,那当前单例模式代码就能应付需求。但是如果单例获取得很频繁,这就需要考虑同步带来的效率降低问题。因为每个需要获取单例的线程都必须排队来等待获取单实例,所以在获取单例时可能会出现卡顿,响应慢等问题。所以在频繁获取单例,且是多线程场景时,我们应该如何进行改善?
方法1:不延迟初始化,直接在类加载时创建对象。如果单例实例化的过程并不会花费大量时间,那在类加载时直接时直接创建也是没问题的,这样就不需要在单例获取的代码中加入条件判断了。
public class Singleton {
private static Singleton uniqueInstance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return uniqueInstance;
}
}
方法二:双重检查锁
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {}
public static Singleton getInstance() {
if (uniqueInstance == null) {
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
这里使用的是同步变量,而不是同步方法,同步块内部的还进行一次判断是因为变量的初始化并不是原子操作,会分为分配栈空间和初始化两个过程,分配栈空间后变量就不是null了,但这时候的子节码是之前内存存入的子节数据,所以会不会执行同步块内部if语句的代码,这才能同步单例创建的过程。
发表回复