Java单例模式如何实现
单例模式属于创建型设计模式,该类设计模式抽象了对象创建、组织的过程,将目标类的信息进行封装,仅向外部提供获取对象的接口。
单例模式确保系统中只包含一个目标类的实例,负责完成其实例化并向外部提供该实例。一般可用于文件创建、数据库连接等辅助类,实现对系统资源的控制。
一般来说,单例模式在实现上具有私有构造方法、私有静态变量、公有返回接口等特点。以下为几种常见的单例模式实现方法。
1.饿汉模式
饿汉模式在类加载时完成实例的创建,不存在线程安全的问题,缺点在于即使未被引用也始终会存在该类的实例。
class Singleton{
private static final Singleton singleton = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return singleton;
}
}
-
注意final,保证引用的实例不变。
2.懒汉模式
懒汉模式在初次获取实例时完成创建并返回,可以实现懒加载,但其缺点在于初次获取时可能需要更长的时间;适用于创建消耗大、实例使用少的情况,存在线程安全问题。
class Singleton{
private static Singleton1 instance = null;
private Singleton() { }
public static Singleton getInstance() {
if (null == instance) {
instance = new Singleton();
}
return instance;
}
}
3.双重检验锁
双重检验锁是适用于多线程模式下的懒汉模式,相比于直接对整个getInstance()方法加锁,可以减少大量进入临界区的性能消耗。
class Singleton{
private static volatile Singleton singleton;
private Singleton(){}
public static Singleton getInstance(){
if (singleton == null){
synchronized (Singleton.class){
if (singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
-
第一层判断是为了在实例已经初始化时直接返回,第二层锁可以防止同时进入临界区的不同线程多次实例化对象。
-
注意volatile:
-
由于singleton = new Singleton();并非原子操作,而是由分配内存空间、初始化对象以及将引用指向堆对象三个步骤完成,而在JVM执行时可能产生重排序,使得引用变量指向了未初始化的堆对象,导致另一个线程判断失败(第一层)获取到该未初始化的实例;如下图所示:
-
-
volatile关键字可以保证线程之间的可见性及有序性,使所有写操作先于读操作执行,避免上述问题。
-
4.利用静态内部类
-
线程安全:与懒汉模式相同,利用类的加载保证单例对象只被实例化一次;
-
懒加载:由于一个类只有在其静态成员初次被引用时才会被加载,即调用Singleton.getInstance()时内部类SingletonHolder才会被加载,同时初始化instance。
-
反射安全:通过反射不能从外部类中获取内部类的属性,即不可对instance进行修改。
public class Singleton{
private static class SingletonHolder{
public static Singleton instance = new Singleton();
}
private Singleton(){}
public static Singleton getInstance(){
return SingletonHolder.instance;
}
}
5.利用枚举Enum
在Java中,枚举可以看作一个类,其中也可以定义方法;而枚举中的变量可以看作该类的一个实例。枚举的构造方法限制为私有,在初次访问枚举实例时执行;而枚举中的实例定义为static final,从而只可实例化一次。
class SingleTon{}
enum SingleEnum{
myEnum; // 枚举实例
private SingleTon singleTon;
SingleEnum() { // 构造方法,默认为private
singleTon = new SingleTon();
}
public SingleTon getSingle(){
return singleTon;
}
}
实例的获取:
SingleTon s=SingleEnum.myEnum.getSingle();