大话设计模式——单例模式(Singleton)
单例模式(Singleton)是java中一个比较常见的设计模式,单例对象主要是为了保证在一个JVM中,该对象只有一个实例存在。主要优势:
(1) 减少一些大型对象的开销,减少内存的占用
(2) 省去了new操作,较少了GC的压力
(3) 保证了一些核心类只被创建一次,比如一些核心的调度类,一些主要的操作类只被创建一次。
单例模式最常见的两种实现模式:饿汉式,懒汉式,内部静态类实现,双重检验锁模式
1.饿汉式:在进行类加载时就创建好 (添加了synchronized保证了线程的安全性)
package designPatterns; public class SingletonHungry { private static SingletonHungry instance = new SingletonHungry(); //私有构造函数,防止被实例化 private SingletonHungry(){ } public static SingletonHungry getInstance(){ return instance; } }
2. 懒汉式:延迟加载,在用到的时候才进行创建
package designPatterns; public class SingletonLazy { private static SingletonLazy instance = null; //私有构造函数,防止被实例化 private SingletonLazy(){ } public static SingletonLazy getInstance(){ if(instance == null){ instance = new SingletonLazy(); } return instance; } }
当然懒汉模式是线程不安全的,为了保证线程安全,我们可以将函数定义成synchronized的,但是实际上,并不是每次都需要同步的,只是在第一次创建对象的时候需要确保同步,因此我们可以只同步一个对象。如下
package designPatterns; public class SingletonLazy { private static SingletonLazy instance = null; //私有构造函数,防止被实例化 private SingletonLazy(){ } /* public static SingletonLazy getInstance(){ if(instance == null){ instance = new SingletonLazy(); } return instance; } */ public static SingletonLazy getInstance(){ if(instance == null){ synchronized(instance){ if(instance == null){ instance = new SingletonLazy(); } } } return instance; } }
但是,由于java中创建对象和赋值操作是分开进行的,也就是说instance = new SingletonLazy()这句话是分步执行的。JVM的即时编译存在指令重派的优化,2,3步的操作是无法确定的。
1. 给instance分配内存
2. 调用构造函数初始化成员变量
3. 将instance对象指向分配的内存空间(执行完这句话之后instance才会变成非null的)
两步执行的,可能先执行分配空间,再初始化,这也可能会导致A,B两个线程不同步,所以还有一种方法是使用内部静态类来进行实现,内部静态类在另一篇博客中有详细介绍,这里不再赘述。
3. 静态内部类,代码如下:
package designPatterns; public class Singleton { //私有构造方法,防止被实例化 private Singleton(){ } //静态内部类创建实例 private static class SingletonFactory{ private static Singleton instance = new Singleton(); } //获取实例 public static Singleton getInstance(){ return SingletonFactory.instance; } //如果该对象被用于序列化,可以保证对象在序列化前后保持一致 public Object readResolve(){ return getInstance(); } }
使用内部静态类的方法,只有在外部类被调用时才会被加载,然后new一个实例,这样就避免了锁的应用,一般建议使用这种方法,但还是需要根据实际需要来进行选择的。
4. 双重检验锁模式(虽然内部类的方法感觉更加好用一些,但是很多面试官想让你写的还是双检锁模式)
public class Singleton { private volatile static Singleton instance; //volatile的可见性 private Singleton() {} public static Singleton getSingleton() { if(instance == null) { //Single Check synchronized(Singleton.class) { if(instance == null) { //Double Check instance = new Singleton(); } } } return instance; } }
双检锁模式其实就是为了保证一个顺序。双检锁的出现保证了上述2,3句的执行顺序。