java 单例设计模式

作者: 疯狂的冰块 | 来源:发表于2017-08-21 02:02 被阅读86次

    单例是应用开发中一种设计模式,主要应用场景为:当且仅当系统中只能保留一个对象时使用。本文提出4中可以在生产环境中使用的单例设计模式。推荐使用enum的方式。

    应用场景

    例如一下应用场景[1]
    1、 Windows的Task Manager(任务管理器)就是很典型的单例模式(这个很熟悉吧),想想看,是不是呢,你能打开两个windows task manager吗?

    2、网站的浏览人数统计,一般也是采用单例模式实现,否则难以同步。

    3、应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。

    //todo
    在joshua block 的《effective java second edition》 一书中给出了三种单例设计模式

    1、采用静态变量:

    public class TaskManager {
            public static final TaskManager INSTANCE = new TaskManager ();
            private TaskManager (){}
            //...
    }
    

    这种写法使用了私有的构造方法。来保证只能有一个实例,但是这种方法也有例外情况,因为,你可以通过反射来调用私有构造方法。这个时候你可以抛出异常。以下代码仅作为参考。

    public class TaskManager {
        public static final TaskManager INSTANCE = new TaskManager();
    
        private TaskManager() {
            if (INSTANCE != null) {
                try {
                    throw new Exception("An object already exists");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        //...
    }
    

    2、采用静态方法

    public class TaskManager {
        private static final TaskManager INSTANCE = new TaskManager();
    
        private TaskManager() {}
    
        public static TaskManager getINSTANCE() {
            return INSTANCE;
        }
    
        //...
    }
    

    3、采用enum的方式

    这种模式是目前最佳的,因为:
    1、JVM会保证enum不能被反射并且构造器方法只执行一次。
    2、此方法无偿提供了序列化机制,绝对防止反序列化时多次实例化。
    3、运行时(compile-time )创建对象(懒加载) // todo 关于cmpile-time和run-time有时间我单独写一篇文章。

    enum是jdk5的特性,现在(2017)web应用普遍在jdk6、7、8,所以可以放心使用。

    目前最佳的方式是使用接口的方式(解耦):

    interface Resource {
        Object doSomething();
    }
    
    public enum SomeThing implements Resource {
        INSTANCE {
            @Override
            public Object doSomething() {
                return "I am a Singleton nstance";
            }
        };
    }
    
    class Demo {
        public static void main(String[] args) {
            System.out.println(SomeThing.INSTANCE.doSomething());
        }
    }
    

    或者不使用接口的形式

    public enum SomeThing {
        INSTANCE;
    
        public void doSomething() {
            System.out.println("INSTANCE = " + INSTANCE);
        }
    
    }
    
    class Demo {
        public static void main(String[] args) {
            SomeThing.INSTANCE.doSomething();
        }
    }
    

    也有人用其他的方式,我对这种方法持强烈反对,具体可以参考文献4,以下代码仅做参考

    class Resource {
    }
    
    public enum SomeThing {
        INSTANCE;
        private Resource instance;
    
        SomeThing() {
            instance = new Resource();
        }
    
        public Resource getInstance() {
            return instance;
        }
    }
    
    class Demo {
        public static void main(String[] args) {
            System.out.println(SomeThing.INSTANCE.getInstance());
        }
    }
    

    在其他文章中有提到“懒汉”、“恶汉”的名词,其实懒汉主要就是"懒"加载[注:指在使用时装载,不使用时不进行装载]

    有人提出这种懒汉设计

    public class Singleton {  
        private static Singleton instance;  
        private Singleton (){}  
      
        public static Singleton getInstance() {  
        if (instance == null) {  
            instance = new Singleton();  
        }  
        return instance;  
        }  
    }
    

    很显然这种设计线程不安全,一般不会使用。
    有人又提出了懒汉改进的方法,使其线程安全。

    public class Singleton {  
        private static Singleton instance;  
        private Singleton (){}  
        public static synchronized Singleton getInstance() {  
        if (instance == null) {  
            instance = new Singleton();  
        }  
        return instance;  
        }  
    }  
    

    这种写法能够在多线程中很好的工作,而且看起来它也具备很好的lazy loading,但是,遗憾的是,因为是重量级锁,效率很低。

    于是有人提出了双重校验锁机制,这个用的也比较多。

    下面代码就是用double checked locking 方法实现的单例,这里的getInstance()方法要检查两次,确保是否实例INSTANCE是否为null或者已经实例化了,这也是为什么叫double checked locking 模式。

    /**
    * Singleton pattern example with Double checked Locking
    */
    public class DoubleCheckedLockingSingleton{
         private volatile DoubleCheckedLockingSingleton INSTANCE;
     
         private DoubleCheckedLockingSingleton(){}
     
         public DoubleCheckedLockingSingleton getInstance(){
             if(INSTANCE == null){
                synchronized(DoubleCheckedLockingSingleton.class){
                    //double checking Singleton instance
                    if(INSTANCE == null){
                        INSTANCE = new DoubleCheckedLockingSingleton();
                    }
                }
             }
             return INSTANCE;
         }
    }
    

    参考文献:
    [1] Jason Cai, 设计模式之——单例模式(Singleton)的常见应用场景
    [2] cantellow, 单例模式的七种写法
    [3] Javarevisited, 单例模式中为什么用枚举更好
    [4] natsumi, Java枚举实现单例模式
    [5] zejian_深入理解Java枚举类型(enum)

    相关文章

      网友评论

        本文标题:java 单例设计模式

        本文链接:https://www.haomeiwen.com/subject/rivqdxtx.html