zoukankan      html  css  js  c++  java
  • 【转载】单例模式的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中,进行统一管理,更加方便快捷。



  • 相关阅读:
    linux inode索引节点使用率100% 解决
    Linux常用命令
    mongodb常用命令
    抓包工具简介:fiddler、charles
    博客园自定义更换背景
    ant+jmeter应用
    BeanShell断言
    jmeter 常用函数(一):__Random
    git常见错误解决方法
    react环境搭建
  • 原文地址:https://www.cnblogs.com/dawnyxl/p/9468300.html
Copyright © 2011-2022 走看看