设计模式-创建型-单例模式(Singleton)

  单例模式是面试的时候问得比较多的一种设计模式,这个设计模式比较简单,但其实要真的实现单例,并且对JVM比较友好的话,可能需要注意几点。

  单例模式,就是保住在内存中,类的实例唯一。我们知道,用new关键就会产品一个新的实例。要保证内存中只有一个实例,则不能在外部用new关键字进行对象的新建,则该类的构造方法只能用private修饰;不能再外部调用构造器创建对象,也就不存在使用实体来调用方法创建对象一说;所以只能用类调用static静态方法,来获取对象。这个对象不能在外部创建,则只能在类中进行创建

  为了只有一个实例产生,而不是每次调用static方法的时候都创建一个类,则该类中必须要有该对象的一个缓冲。因此需要一个成员变量来缓存生产的实例。由于该实例是由上面所说的static方法构建的,并且该static方法需要访问缓存的实例,所以该成员变量需要由static修饰。

  懒汉式

public class Singleton {
    private static Singleton singleton = null;
    private Singleton() {}
    public static Singleton getSinleton() {
        if(singleton==null) {                      
            singleton = new Singleton();           
        }
        return singleton;
    }
}

  对多线程了解就会知道,在多线程的情况下,这个方式无法保证在内存只生产一个实例。当两个线程都运行到if(singleton==null) 时,会产生两个Singleton 的实例对象。

懒汉式(升级)

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

  这种方式只是在生产单例的方法上面加上了synchronized,同步方式。每次访问单例的实例是,都需要使用同步方式,效率不高。若单纯的将同步方法改成同步代码块,存在同样的问题。

懒汉式(再升级)

public class Singleton {
    private static volatile Singleton singleton = null;
    private Singleton() {}
    public  static  Singleton getSinleton() {
        if(singleton==null) {
            synchronized(Singleton.class) {
                if(singleton==null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

  这种模式,只有在第一次访问单例的实例对象是,才会对Singleton类进行加锁,优化了上面的程序。

  这种方法就完美的结果了上面的问题,但是情况真的是这样吗?

  由于程序的无序写,当一个线程运行到singleton = new Singleton()是,存在两个步骤,这行其实做了两个事情:1、调用构造方法,创建了一个实例。2、把这个实例赋值给singleton这个实例变量。可问题就是,这两步jvm是不保证顺序的。也就是说。可能在调用构造方法之前,singleton已经被设置为非空了。由于无序写,若只完成了singleton非空(未调用构造函数,没有创建实例对象),这时线程退出;另一个线程进入时,第一次判断singleton==null是否为真,此时singleton不为空,该线程返回了一个不为空,但是没有进行初始化的对象;原来的线程再完成调用构造函数,初始化,则程序异常(引用https://blog.csdn.net/u011499747/article/details/48194431)。

懒汉式(那就再升级罗)

public class Singleton {
    private static Singleton singleton = null;
    private Singleton() {}
    public  static  Singleton getSinleton() {
        if(singleton==null) {
            synchronized(Singleton.class) {
                Singleton temp = singleton;
                if(temp==null) {
                    temp = new Singleton();
                }
                singleton = temp;
            }
        }
        return singleton;
    }
}

  好了,这样也避免了无序写的问题了。但是这样编程就显得非常笨重,其实java为了避免这种无需写带来的问题,体统了一个处理方案,那就是使用volatile关键字,在懒汉式(再升级)的例子中,只需要加上一个volatile关键字就可以了

public class Singleton {
    private static volatile Singleton singleton = null;
    private Singleton() {}
    public  static  Singleton getSinleton() {
        if(singleton==null) {
            synchronized(Singleton.class) {
                if(singleton==null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

  这里面就要说到volatile的作用,这里用到的就是防止指令重排,参考https://blog.csdn.net/linjpg/article/details/80777145

  其实上面两种单例模式的写法都是为了防止指令重排导致程序存在的问题,不过使用volatile编程看起来就更加简洁些。

 

  好了,一个懒汉式单例模式现在就写得差不多了,现在谈一下饿汉式。

  饿汉式

public class Singleton {
    //单例变量 ,static的,在类加载时进行初始化一次,保证线程安全
    private static final Singleton singleton = new Singleton();
    private Singleton() {};
    public static Singleton getSingleton() {
        return singleton;
    }
}

  简单,没有多线程问题,爽。这个地方使用final,也是说这个类初始化之后,不可更改。不过总能骨头里面挑刺。要是构造的单例很大,构造完又迟迟不使用,会导致资源浪费。

  下面这种方式,即使加载了Singleton,而不需要使用它的实例时,是不会进行初始化工作的。

饿汉式(内部实现懒汉式)

public class Singleton {
    public static class SingletonHolder{
        private static final Singleton singleton = new Singleton();
    }
    
    private Singleton() {};
    public static Singleton getSingleton() {
        return SingletonHolder.singleton;
    }
}

 

java的创始人之一Joshua Bloch在《Effective Java》中,写了一种单例的方法

枚举单例

public enum Singleton {
    INSTANCE;
    public  void m(){}
}

  不仅解决了线程同步,还可以防止反序列化。

  单例模式设计完成。

 

  扩展,单例模式是否就可以保证系统运行时,JVM的一个命名空间中只有一个实例。

    反射和反序列化,这部分后续会添加。

posted on 2019-05-22 23:08  xingshouzhan  阅读(195)  评论(0编辑  收藏  举报

导航