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

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

//饿汉式
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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值