Java 单例模式
单例模式
简单介绍
单例模式,最简单的创建行设计模式;主要作用是对实例的生成过程进行管理.以保证在应用程序的整个生命周期中,任何一个时刻,所希望的实例只有一个.
单例模式在程序的执行生命周期中确保了单例类只有一个实例,自行实例化这个实例,并向整个系统提供这个实例.
单例模式的特点
- 单例的类只能有一个实例
- 单例类自行创建这个实例,类外是无法创建的
- 单例类自行向整个系统提供这个实例
单例的实现
经典的单例
1 /** 2 * @Title:经典单例模式代码 3 * 4 * @author Kevin 5 * 6 */ 7 public class Singleton { 8 private static Singleton singleton = null; 9 10 private Singleton() { 11 12 } 13 14 public static Singleton getSingletonInstance() { 15 if (singleton == null) { 16 singleton = new Singleton(); 17 } 18 return singleton; 19 } 20 }
通过上例讲解经典的单例模式:
经典的单例模式可以理解为最为符合单例模式特点的实现单例的方法:
从单例模式的特点出发
首先单例模式在整个程序的生命周期中只能有一个实例
所以使用静态的全局私有变量来保存这个类的唯一性,对应单例模式的第一个特点
private static Singleton singleton = null
并使用私有的构造方法使单例类外部无法使用单例类的构造方法生成实例为外部类所用,阻止在单例类外部通过new方法来创建实例
private Singleton(){
}
最后,使用公开的(public)静态(static)工厂方法,并返回此单例类(Singleton)的唯一实例(singleton);
并且,方法中要进行一次关于singleton的检测,singleton变量是否已经初始化,若已经初始化,则直接返回singleton这个实例;
若没有初始化,则new出一个Singleton实例,并将之保存到singleton变量,并且将返回这个实例变量.
自行创建这个实例,并向整个系统提供公共的这个实例,对应单例模式的第二和第三个特点。
public static Singleton getSingletonInstance(){
if(singleton == null){
singleton = new Singleton();
}
return singleton;
}
饿汉式的单例
1 /** 2 * @Title:饿汉式单例模式代码 3 * 4 * @author Kevin 5 * 6 */ 7 public class Singleton { 8 private static final Singleton singleton = new Singleton(); 9 10 private Singleton() { 11 12 } 13 14 public static Singleton getSingletonInstance() { 15 return singleton; 16 } 17 }
饿汉式的特点为,使用java关键字final修饰变量singleton,致使只能在单例类初始化的时候或者在构造方法中分配singleton变量;
所以,单例类在加载的时候,静态变量songleton会被初始化,私有的构造方法就被调用,创建单例类的唯一实例。
饱汉式的单例
1 /** 2 * @Title:饱汉式单例模式代码 3 * 4 * @author Kevin 5 * 6 */ 7 public class Singleton { 8 private static Singleton singleton = null; 9 10 private Singleton() { 11 12 } 13 14 public static synchronized Singleton getSingletonInstance() { 15 if (singleton == null) { 16 singleton = new Singleton(); 17 } 18 return singleton; 19 } 20 }
懒汉式的单例模式跟经典单例模式很相像,包括类开始加载静态私有变量的声明,私有构造方法都跟经典模式的单例完全一模一样,
在对外提供公有的单例提供方法时,使用synchronized关键字,将不同线程对getSingletonInstance()的访问进行同步;
正是由于这个synchronized关键词的锁定,在某些情况下会造成程序性能的下降,影响系统性能.
而且,需要考虑的是,正因为这个synchronized关键字对线程进行了控制,懒汉式单例的类在实例化时要对多个线程同时首次引用
自身单例进行线访问限制处理,特别在当单例类在作为资源控制器在实例化的时候,必然要涉及到资源初始化,而资源初始化是会消耗
很多时间的.这就意味着出现多个线程同时首次引饱汉式单例类的几率变大.
懒汉式单例与饿汉式单例进行对比,饿汉式单例类在类被加载的时候就将自己实例化(在加载器是静态的时候也不例外)
这样,由于饿汉式单例忽略了类加载器的状况,统一的在所有情况下都进行自行实例化,资源的利用效率要比饱汉式单例要低,而从代码的
易写和可读性还有速度以及代码的反应时间方面来考虑,饿汉式单例的优势还是很大的!
双重检查的单例设计模式
1 /** 2 * @Title:双重检查单例模式代码 3 * 4 * @author Kevin 5 * 6 */ 7 public class Singleton { 8 private static Singleton singleton = null; 9 10 private Singleton() { 11 12 } 13 14 public static Singleton getSingletonInstance() { 15 if (singleton == null) { 16 synchronized (Singleton.class) { 17 if (singleton == null) { 18 singleton = new Singleton(); 19 } 20 } 21 singleton = new Singleton(); 22 } 23 return singleton; 24 } 25 }
在if (singleton == null) 处进行第一次检查
第二个if (singleton == null) 处进行第二次检查
缺陷分析
假设线程1和线程2作为第一批调用者同时调用静态工厂方法getSingletonInstance(),在第一个if (singleton == null) 处进行第一次检查,
因为此时singleton为null,因为1,2两个线程会同时到达synchronized (Singleton.class) 这里,假设线程1先通过synchronized(Singleton.class),
由于同步话的限制,2线程会在此等候;
线程1执行singleton = new Singleton(),得到了一个singleton[Singleton对象的引用],此时2线程仍旧在等候,
当线程1退出synchronized (Singleton.class),返回singleton,并且退出静态工厂方法,此时2线程进入synchronized (Singleton.class),准备进行第二个检查
if (singleton == null),由于刚刚已经得到了一个Singleton对象的引用singleton所以if条件判断不成立,直接返回singleton[线程A所创建的对象],退出静态工厂方法
可以看出,这个流程并不是我们所想要的顺序双重检查的单例静态工厂方法只起到了避免多个线程同时初始化这个类,而没有达到避免多个线程同时吊用这个静态工厂方法,
而且这样进行的逻辑是有问题的.在Singleton类初始化的时候与singleton变量赋值的顺序是不可确定的,如果某一线程再没有同步化的条件下读取songleton的引用,并且调用
这个对象的方法,可能会因为对象的初始化过程尚未完成而造成崩溃!
双重检查的单例设计模式
1 /** 2 * @Title:双重检查优化单例模式代码 3 * 4 * @author Kevin 5 * 6 */ 7 public class Singleton { 8 private volatile static Singleton singleton = null; 9 10 private Singleton() { 11 12 } 13 14 public static Singleton getSingletonInstance() { 15 if (singleton == null) { 16 synchronized (Singleton.class) { 17 if (singleton == null) { 18 singleton = new Singleton(); 19 } 20 } 21 singleton = new Singleton(); 22 } 23 return singleton; 24 } 25 }
优化后,使用了关键字volatile,来确保singleton被初始化为单例后的改变对所有线程均有效,多线程能够正确处理singleton变量,
getSingletonInstance()中包括两次对singleton是否为null进行半段,第一次判断是所有访问都会执行的,第二次的判断只在初始访问中存在大量并发线程的时候在进行判断
这样通过两次判断,避免了不必要的线程同步