单例模式以及七种实现方式

作者:KaelQ
链接:https://www.jianshu.com/p/ad86f107425c
单例模式以及七种实现方式

1.定义

  • 一个类只有一个实例,并且只有一个全局获取入口。

2.适用场景

  • 某个实例对象频繁被访问。
  • 某个实例占用的资源较多。

3.实现方式

3.1 懒汉模式(线程不安全)

这样可能会出现线程不同的方法,所以必须对getSingleton方法进行同步。

public class Singleton {
private static Singleton singleton;//私有静态变量
private Singleton(){};//私有构造方法
//全局静态访问入口
public static Singleton getSingleton(){
if(singleton==null){
singleton=new Singleton();
}
return singleton;
}
}

3.2 懒汉模式(线程安全)

public class Singleton { 

private static Singleton singleton;//私有静态变量
private Singleton(){};//私有构造方法
//全局静态访问入口
public static synchronized Singleton getSingleton(){
if(singleton==null){
singleton=new Singleton();
}
return singleton;
}
}

这样子线程就安全了,但是消耗了不必要的同步资源,不推荐这样使用。

3.3 饿汉模式(线程安全)

public class Singleton {
private static Singleton singleton=new Singleton();//私有静态变量
private Singleton(){};//私有构造方法
//全局访问入口
public Singleton getSingleton(){
return singleton;
}
}

避免线程安全问题,在声明私有静态变量时,就已经实例化了类,不用考虑线程同步问题。

3.4 DCL模式(Double CheckLock)

public class Singleton {
private static Singleton singleton;//私有静态内部变量
private Singleton(){};//私有构造方法
//全局访问入口
public static synchronized Singleton getSingleton(){
if(singleton==null){

synchronized (Singleton.class){
if(singleton==null){
singleton=new Singleton();
}
}
}
return singleton;
}
}

通过两个判断,第一层是避免不必要的同步,第二层判断是否为null。

可能会出现DCL模式失效的情况。

DCL模式失效:

singleton=new Singleton();这句话执行的时候,会进行下列三个过程:

  1. 分配内存。
  2. 初始化构造函数和成员变量。
  3. 将对象指向分配的空间。

由于JMM(Java Memory Model)的规定,可能会出现1-2-3和1-3-2两种情况。

所以,就会出现线程A进行到1-3时,就被线程B取走,然后就出现了错误。这个时候DCL模式就失效了。

Sun官方注意到了这种问题,于是就在JDK1.5之后,具体化了volatile

关键字,这时候只用调整一行代码即可。

private volatile static Singleton singleton;

3.5 静态内部类模式

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

第一次加载Singleton类不会加载SingletonHolder类,但是调用getSingleton时,才会加载SingletonHolder,才会初始化singleton。即确保了线程安全,又保证了单例对象的唯一性,延迟了单例的实例化。这是最推荐的方式。

3.6 枚举模式

public enum Singleton{
singleton;
public void hello(){
System.out.print("hello");
}
}

这样很简单,线程时安全的,并且避免了序列化和反射攻击。

除了枚举模式,其他模式在实现了Serializable接口后,反序列化时单例会被破坏。所以要重写readResolve()方法。

private Object readResolve() throws ObjectStreamException {
return INSTANCE;
}

3.7 使用容器实现单例模式

public class SingletonManger{
private static Map objectMap=new HashMap();
private SingletonManger(){}
public static void registerService(String key,Object singleton){
if(!objectMap.containsKey(key)){
objectMap.put(key,singleton);
}
}
public static Object getObjectService(String key){
return objectMap.get(key);
}
}

这样可以将多个单例对象注入到HashMap中,进行统一管理,更加方便快捷。


分享到:


相關文章: