zoukankan      html  css  js  c++  java
  • 枚举实现的单例模式

    常见单例

    在用枚举实现单例模式之前,先用常见的方式来实现这些单例模式

    /**   
     * 实现单例访问Kerrigan的第一次尝试   
     */    
    public class SingletonKerriganA {     
          
        /**   
         * 单例对象实例   
         */    
        private static SingletonKerriganA instance = null;     
          
        public static SingletonKerriganA getInstance() {     
            if (instance == null) {                              //line A     
                instance = new SingletonKerriganA();          //line B     
            }     
            return instance;     
        }     
    }     

    这种实现方式存在一个严重的问题,就是多线程问题,假设场景

    两个线程并发调用SingletonKerriganA.getInstance(),假设线程一先判断完instance是否为null,既代码中的line A进入到line B的位置。刚刚判断完毕后,JVM将CPU资源切换给线程二,由于线程一还没执行line B,所以instance仍然是空的,因此线程二执行了new SignletonKerriganA()操作,这时会创建两个对象

    于是我们对上述代码进行了改进:

    /**   
     * 实现单例访问Kerrigan的第二次尝试   
     */    
    public class SingletonKerriganB {     
          
        /**   
         * 单例对象实例   
         */    
        private static SingletonKerriganB instance = null;     
          
        public synchronized static SingletonKerriganB getInstance() {     
            if (instance == null) {     
                instance = new SingletonKerriganB();     
            }     
            return instance;     
        }     
    }  

    在第一个的代码基础之上,很容易发现,我们给这个方法添加了内置锁synchronized。

    读者读到这,可能觉得已经差不多了,保证了单例,保证了线程安全,但是仔细想想,这段代码仍然存在不少问题,当有大量线程访问时,存在性能问题,串行化执行的,容易形成阻塞,想一想:我们其实只需要在第一次创建的时候给代码加锁:

    于是代码又成了这样

        
        /**   
         * 单例对象实例   
         */    
        private static SingletonKerriganD instance = null;     
          
        public static SingletonKerriganD getInstance() {     
            if (instance == null) {     
                synchronized (SingletonKerriganD.class) {     
                    if (instance == null) {     
                        instance = new SingletonKerriganD();     
                    }     
                }     
            }     
            return instance;     
        }     
    }    

    但是这样写也并非完美的:

    假设线程一执行到instance = new SingletonKerriganD()这句,这里看起来是一句话,但实际上它并不是一个原子操作(原子操作的意思就是这条语句要么就被执行完,要么就没有被执行过,不能出现执行了一半这种情形)。事实上高级语言里面非原子操作有很多,我们只要看看这句话被编译后在JVM执行的对应汇编代码就发现,这句话被编译成8条汇编指令,大致做了3件事情: 
     
    1.给Kerrigan的实例分配内存。 
     
    2.初始化Kerrigan的构造器 
     
    3.将instance对象指向分配的内存空间(注意到这步instance就非null了)
    于是再想,既然我们创建存在问题,倒不如,把创建对象的事直接交给jvm来做,更可靠,在类加载阶段,就创建好对象。
    /**   
     * 实现单例访问Kerrigan的第六次尝试   
     */    
    public class SingletonKerriganF {     
          
        private static class SingletonHolder {     
            /**   
             * 单例对象实例   
             */    
            static final SingletonKerriganF INSTANCE = new SingletonKerriganF();     
        }     
          
        public static SingletonKerriganF getInstance() {     
            return SingletonHolder.INSTANCE;     
        }     
    }    

    到这里先告一段落:

    我们来看以下问题:

    单例实现序列化接口

    public class SingletonKerrigan implements Serializable {     
          
        private static class SingletonHolder {     
            /**   
             * 单例对象实例   
             */    
            static final SingletonKerrigan INSTANCE = new SingletonKerrigan();     
        }     
          
        public static SingletonKerrigan getInstance() {     
            return SingletonHolder.INSTANCE;     
        }     
          
        /**   
         * private的构造函数用于避免外界直接使用new来实例化对象   
         */    
        private SingletonKerrigan() {     
        }     
          
        /**   
         * readResolve方法应对单例对象被序列化时候   
         */    
        private Object readResolve() {     
            return getInstance();     
        }     
    }    
    public class Test01 {
        
        public static void main(String[] args) throws Exception{
           
           Instance i1 = Instance.getInstance();
           ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("b.txt"));
           oos.writeObject(i1);
           oos.flush();
           
           ObjectInputStream ois = new ObjectInputStream(new FileInputStream("b.txt"));
           Instance instance = (Instance) ois.readObject();
           System.out.println(instance == i1);
           ois.close();
           
        }
    }
    控制台打印的结果是flase

    可以看出问题了,在实现序列化和反序列化时,并不能保证对象的唯一性:

    这时需要在单例类中提供 这个方法:

     private Object readResolve() {     
            return getInstance();     
        }  

    写到这儿:我们是不是对实现单例模式感到绝望呢?

    别灰心:《Effective Java》书中,作者为我们提供了一种实现单例模式的最好的方式

    public class SingletonKerrigan {     
          
        /**   
         * 单例对象实例   
         */    
        INSTANCE;     
                  
        private SingletonKerrigan (){}
        
    }     
  • 相关阅读:
    JNI实例(含代码)
    sizeof()小结
    【转】C++ 关键字——friend
    curl资料小结
    【Curl (libcurl) 开发 之一】Cocos2dx之libcurl(curl_easy)的编程教程(帮助手册)!
    JSP中统一错误信息输出
    在号码池取连续号码的算法
    常用的文件操作方法
    走出基金净值的误区
    ResultSet概论
  • 原文地址:https://www.cnblogs.com/clovejava/p/7892648.html
Copyright © 2011-2022 走看看