【转载】单例模式的7种写法(整合自两篇文章)

转载出处:

1)http://www.blogjava.net/kenzhh/archive/2013/03/15/357824.html

这篇文章介绍了7种单例模式写法,其实是5种:懒汉,恶汉,双重校验锁,枚举和静态内部类。

2)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;
    }
}

这种写法能够在多线程中很好的工作,而且看起来它也具备很好的lazy loading,但是,遗憾的是,效率很低,99%情况下不需要同步。

3.3 饿汉模式(线程安全)

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

这种方式基于classloder机制避免了多线程的同步问题,不过,singleton在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用getSingleton方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化singleton显然没有达到lazy loading的效果。

3.4 静态内部类模式

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

这种方式同样利用了classloder的机制来保证初始化singleton时只有一个线程,它跟第三种方式不同的是(很细微的差别):第三种方式是只要Singleton类被装载了,那么singleton就会被实例化(没有达到lazy loading效果),而这种方式是Singleton类被装载了,singleton不一定被初始化。因为SingletonHolder类没有被主动使用,只有显示通过调用getSingleton方法时,才会显示装载SingletonHolder类,从而实例化singleton。想象一下,如果实例化singleton很消耗资源,我想让他延迟加载,另外一方面,我不希望在Singleton类加载时就实例化,因为我不能确保Singleton类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化singleton显然是不合适的。这个时候,这种方式相比第三方式就显得很合理。

3.5 枚举模式

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

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

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

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

3.6 双重校验锁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.7 使用容器实现单例模式

public class SingletonManger{
    private static Map<String,Object> objectMap=new HashMap<String,Object>();
    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中,进行统一管理,更加方便快捷。



猜你喜欢

转载自www.cnblogs.com/dawnyxl/p/9468300.html
今日推荐