单例模式是面试的时候问得比较多的一种设计模式,这个设计模式比较简单,但其实要真的实现单例,并且对JVM比较友好的话,可能需要注意几点。
单例模式,就是保住在内存中,类的对象唯一。我们知道,用new关键就会产品一个新的对象。要保证内存中只有一个对象,则不能在外部用new关键字进行对象的新建,只能用类调用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 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、把这个实例赋值给instance这个实例变量。可问题就是,这两步jvm是不保证顺序的。也就是说。可能在调用构造方法之前,instance已经被设置为非空了。由于无序写,若只完成了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; } }
好了,这样也避免了无序写的问题了,这就是一个简单的单例模式了。一般程序到这一步就可以了,当然,程序根据反射和序列化其实也可行创建多个对象的,这个后续会说明。
好了,一个懒汉式单例模式现在就不过多地写了,现在谈一下饿汉式。
饿汉式
public class Singleton { //单例变量 ,static的,在类加载时进行初始化一次,保证线程安全 private static Singleton singleton = new Singleton(); private Singleton() {}; public static Singleton getSingleton() { return singleton; } }
简单,没有多线程问题,爽。不过总能骨头里面挑刺。要是构造的单例很大,构造完又迟迟不使用,会导致资源浪费。
饿汉式(内部实现懒汉式)
public class Singleton { public static class SingletonHolder{ private static Singleton singleton = new Singleton(); } private Singleton() {}; public static Singleton getSingleton() { return SingletonHolder.singleton; } }
哎呀,终于有可以轻松下了。
单例模式设计完成。
其实要挑刺,总是能挑的,后面会再次对单例模式进行挑刺,后面将继续说明。