【剑指offer Java】面试题2:实现Singleton模式

来源:互联网 发布:程序员用浏览器 编辑:程序博客网 时间:2024/05/17 23:41

题目:设计一个类,我们只能生成该类的一个实例。

//饿汉式public static class Singleton01{        //预先初始化static变量        private final static Singleton01 INSTANCE = new Singleton01();        private Singleton01(){        }        public static Singleton01 getInstance(){            return INSTANCE;        }    }

1.优点:线程安全,因为static类在类加载时只初始化一次,保证线程安全

2.缺点:一来就创建实例化对象,不管后面这个对象用不用的到,所以缺点是若构造的实例很大,而构造完又不用,会导致资源的浪费

//懒汉式,无同步锁public static class Singleton02{        private static Singleton02 instance = null;        //私有化构造方法,保证外部类不能通过此构造器来实例化        private Singleton02(){        }        //当多线程同时调用getInstance()时,可能创造多个实例对象,并且将多个实例对象赋值给实例变量instance        public static Singleton02 getInstance(){            if(instance == null)                instance = new Singleton02();            return instance;        }    }
  1. 优点:懒汉式只有在用到的时候(即调用getInstance()方法)才创建实例化对象,避免资源浪费
  2. 缺点:1、若初始化非常耗时时,会造成资源浪费,2、非线程安全,多线程可能赵成多个实例对象被初始化
//懒汉式,同步锁public static class Singleton03{        private static Singleton03 instance = null;        private Singleton03(){        }        /*         * 获得单例对象实例,同步互斥访问实现线程安全         */        public static synchronized Singleton03 getInstance(){            if(instance == null)                instance = new Singleton03();            return instance;        }    }

优点:1、当要使用时才实例化单例,避免资源浪费。2、线程安全
缺点:1、若初始化实例时耗时,则会造成性能降低。2、每次调用getInstance()都获得同步锁,性能消耗(但却无法避免)

 针对Singleton03出现的每次调用getInstance()都获得同步锁而出现的性能问题,那么将synchronized关键字放到getInstance()调用函数里面,如下面Singleton04()代码所示。 但随之而来的问题是:多线程下同时调用getInstance(),这时instance都为空,多线程都进入了synchronized块创建实例,就和前面的Singleton03一样了
//懒汉式,试图将同步锁放在判断是否为单例(instance == null)之后public static class Singleton04{        private static Singleton04 instance = null;        private Singleton04(){        }        public static Singleton04 getInstance(){            if(instance == null){                synchronized (Singleton04.class){                    instance = new Singleton04();                }            }            return instance;        }    }
  为了解决Singleton03()、Singleton04()带来的可能创建多实例对象问题,所以用双重校验锁。  之所以有两个(instance == null)判断是因为考虑了多线程问题(这也是所谓的双重校验锁),当有多个线程同时调用getInstance()的时候,这些线程都能进入第一个if(instance == null)判断,然而只有一个线程能进入第二个if(instance == null)来创建对象。所以,若没有第二个(instance == null)判断,那么多个线程将会创建多个对象。如Singleton05()所示:
//懒汉式,双重校验锁public static class Singleton05{        private volatile static Singleton05 instance = null;        private Singleton05(){        }        public static Singleton05 getInstance(){            if(instance == null){                synchronized (Singleton05.class){                    if(instance == null)                        instance = new Singleton05();                }            }            return instance;        }    }
   我们知道,JVM的内存模型中有一个“无序写”(out-of-order writes)机制,而双重校验锁中instance = new Singleton05()中做了两件事:       1、调用构造方法,创建实例化对象。        2、将实例化对象赋值给引用变量instance   因为“无序写”机制,所以步骤1、2可能是乱序的,因此Singleton05中多线程可能因为第一个线程instance = new Singleton05()先赋值了instance,但是实例化对象却没有建立因此下一个线程进入(instance == null)判断时为false,因此直接返回了instance。但是这个instance是没有经过实例化对象赋值而是默认值的,所以是错误的。而这时第一个线程才开始实例化对象,并且把正确的实例化对象赋值给引用instance。 为了解决JVM的“无序写”问题,用临时变量tmp 

PS:其实就是两次(instance == null)判断后面需要synchronized同步锁来进行线程互斥

//懒汉式,双重校验锁public static class Singleton06{        private static Singleton06 instance = null;        private Singleton06(){        }        public static Singleton06 getInstance(){            if(instance == null){                synchronized(Singleton06.class){                    Singleton06 tmp = instance;                    if(tmp == null){                        synchronized(Singleton06.class){                            tmp = new Singleton06();                        }                        instance = tmp;                    }                }            }            return instance;        }    }
//静态方法块只调用一次,省去了if(instance == null)的比较    public static class Singleton07{        private static Singleton07 instance = null;        static{            instance = new Singleton07();        }        private Singleton07(){        }        public static Singleton07 getInstance(){            return instance;        }    }
  使用静态内部类SingletonHolder来实现懒汉式单例,因为JVM中内部类只有在getInstance()方法第一次被调用时才被加载,即实现了懒汉式(lazy).而static内部类只会被加载一次,因此加载过程是线程安全的
//懒汉式,静态内部类实现public static class Singleton08{        private final static class SingletonHolder{            private static final Singleton08 INSTANCE = new Singleton08();        }        private Singleton08(){        }        public static Singleton08 getInstance(){            return SingletonHolder.INSTANCE;        }    }
/* * 总的来说,就是要考虑是否是懒汉式单例?是否线程安全?是否性能最好? * 按照代码的逻辑来考虑,就是是否是懒汉式单例【即(instance == null)】? * 然后考虑是否线程安全【即synchronized的使用】? * 最后考虑性能如何【即sychronized关键字使用是在getInstance()还是在 * (instance == null)之中】 */

参考链接:
(http://blog.csdn.net/derrantcm/article/details/45330779)

(http://www.cnblogs.com/coffee/archive/2011/12/05/inside-java-singleton.html#norma)

0 0