文章介绍了单例模式五种实现的方式,分别是懒汉,饿汉,静态内部类,双重检验锁以及枚举实现方式,并主要关心加载时机以及线程安全。首先,通俗点讲,饿汉就是这个类还没被使用到的时候,实例已经建立好了;而懒汉是使用到的时候才建立对应的实例。线程安全方面主要考虑实例化时候是否确保一个实例,对于单例类中其余方法的线程安全不予考虑。数据库
先来一个最直观的代码:设计模式
public class Singleton { private static Singleton instance = null; private Singleton(){} public static Singleton getInstance(){ //若是尚未被实例化过,就实例化一个,而后返回 if(instance == null){ instance = new Singleton(); } return instance; } }
这段代码里,咱们没有考虑线程安全,因此可能就会产生多个实例,可是这个例子能很好的表达单例模式的思想,就是保持只有一个实例,先理解了基础,咱们再一步步发展。安全
因此线程安全的事情仍是得解决啊~因而有了如下的代码:性能
public class Singleton { private static Singleton instance = null; private Singleton(){} public static synchronized Singleton getInstance(){ //若是尚未被实例化过,就实例化一个,而后返回 if(instance == null){ instance = new Singleton(); } return instance; } }
这段代码只加了一个关键字synchronized用于确保getInstance方法线程安全,可是这种方式问题很大啊,毕竟全部的线程到了这个方法全得排队等着,对性能的损耗很是大,不过不要紧,咱们这里着重先解决掉线程安全的问题,接下来会有办法解决这个效率低下的问题(若是你着急那就直接去看双重校验锁吧...)学习
懒汉模式将实例化的时机放到了须要使用的时候(饿汉是类加载了就有实例),也就是“延迟加载”,相比饿汉,能避免了在加载的时候实例化有可能用不到的实例,可是问题也很明显,咱们要花精力去解决线程安全的问题。spa
饿汉模式相比懒汉模式,在类加载的时候就已经存在一个实例,举个例子,好比数据库链接吧,懒汉就是第一次访问数据库的时候我才去建立一个链接,而饿汉呢,是你程序启动了,类加载好了的时候,我已经有个链接了,你用不用不必定了,因此饿汉的缺点也就出来了:可能会产生不少无用的实例。线程
public class Singleton { //类加载的时候instance就已经指向了一个实例 private static Singleton instance = new Singleton(); private Singleton(){} public static Singleton getInstance(){ return instance; } }
那么加载时机的问题咱们已经说过了,接下来就是线程安全了,代码里咱们并无看见synchronized关键字,那么这种方式是如何确保线程安全的呢,这个就是JVM类加载的特性了,JVM在加载类的时候,是单线程的,因此能够保证只存在单一的实例。设计
首先要说明的是,双重检验锁也是一种延迟加载,而且较好的解决了在确保线程安全的时候效率低下的问题。code
public class Singleton { private static Singleton instance = null; private Singleton(){} public static Singleton getInstance(){ if(instance == null){ synchronized (Singleton.class){ if(instance == null){ instance = new Singleton(); } } } return instance; } }
对比一下最原始的那种线程安全的方法(就是懒汉模式的第二种代码),那种方法将整个getInstance方法锁住,那么每次调用那个方法都要得到锁,释放锁,等待等等...而双重校验锁锁住了部分的代码。进入方法若是检查为空才进入同步代码块,这样很明显效率高了不少。
有人疑问为何instance==null要判断两次吗,那咱们先去掉第二次的判断。
若是两个线程一块儿调用getInstance方法,而且都经过了第一次的判断instance==null,那么第一个线程获取了锁,而后实例化了instance,而后释放了锁,而后第二个线程获得了线程,而后立刻也实例化了instance,这就尴尬了。单例模式就失败了。
因此加上第二次判断后,先进来的线程判断了一下,哦,为空,我建立一个,而后建立一个实例以后释放了锁,第二个线程进来以后,哎?已经有了,那我就不用建立了,而后释放了锁,开开心心的完成了单例模式。blog
懒汉模式须要考虑线程安全,因此咱们多写了好多的代码,饿汉模式利用了类加载的特性为咱们省去了线程安全的考虑,那么,既能享受类加载确保线程安全带来的便利,又能延迟加载的方式,就是静态内部类。Java静态内部类的特性是,加载的时候不会加载内部静态类,使用的时候才会进行加载。而使用到的时候类加载又是线程安全的,这就完美的达到了咱们的预期效果~
public class Singleton { private static class SingletonHolder{ private static Singleton instance = new Singleton(); } private Singleton(){} public static Singleton getInstance(){ return SingletonHolder.instance; } }
JDK1.5提供了一个新的数据类型,枚举。枚举的出现提供了一个较为优雅的方式取代之前大量的static final类型的变量。而这里,咱们也利用枚举的特性,实现了单例模式,这种思路是《Effective Java》中第三条最后一段给出的实现方式,有兴趣的能够看看这本书~
代码简单到没法理解:
public enum Singleton { INSTANCE; }
外部调用由原来的Singleton.getInstance变成了Singleton.INSTANCE了。
这里要注意,原来的class已经换成了关键字enum,可是其实无所谓的,看下继承关系就能知道,其实仍是一个class
并且咱们能够查看Enum的源码:
这里能看到实现了Serializable接口,因此不用考虑序列化的问题(其实序列化反序列化也能致使单例失败的,可是咱们这里不过多研究)。对于线程安全,一样的,加载的时候JVM能确保只加载一个实例。
在单例模式各类设计的方法中,咱们使用到了内部静态类的特性,使用了枚举的特性,因此基础很是重要,单例模式是设计模式之一,而设计模式实际上是对语言特性不足的一面进一步的包装。吸纳基础,工做学习多加思考,设计模式也就天然而然的可以理解。
另外,好多帖子都说利用枚举的方式在团队合做中不常使用,由于须要配合,用了枚举别人不熟悉。这点我是不一样意的,若是由于你们不懂不熟悉而放弃了使用很棒的特性,那么就永远抱着旧的方式停滞不前,JDK的更新也失去了意义。
仍是但愿可以不断的接触Java新鲜的想法,在深厚的基础上迸发不同的思路,而后去解决实际的问题。
以上,若有不妥,还望指正。