什么是设计模式?设计模式又有什么作用?
在漫漫的程序设计道路之上,前辈们通过自己的教训和经验,总结出一套合理且高效的设计思路,我们称之为模式,理解和使用模式来进行项目的开发,能在很大程度上减少时间的无端浪费和以及提高代码的复用性和可扩展性。
今天,我们就来谈一谈java23模式中的一种—单例模式。
单例模式,顾名思义就是只有一个唯一的实例化对象,那么问题就来了,我们都知道任何模式都有其适应的环境才能发挥最大的作用,单例的使用条件就是:这个类,在整个项目中使用的频率十分的高,如果我们频繁的去new这个对象,必然导致很大的内存开销,这显然不是我们想要的结果,于是就有人想到这样一种情况,每次我们只能创建一个实例对象不就解决了上面的问题,这就是单例模式的由来。
一.单例模式–饿汉式
饿汉的意思就是,管你要不要先创建出来在说;详细实现看代码:
私有化构造函数,通过公共方法获取实例化对象。
二.单例模式–懒汉式
懒汉式的意思就是当你需要的时候才创建
从上面两种单例模式的实现上来看懒汉式看起来要好一点,需要才创建这样就不需要提前创建对象。但是,情况并非如此,实际上饿汉式是一种线程安全的写法
三.线程安全问题(synchronized)
为什么饿汉式是安全的而懒汉式是不安全的,例如:当多个线程并发的情况下执行getInstance()的方法的时候,在懒汉式情况下会出现这种情况:
线程一:进入方法getInstance()此时判断为null,开始进行初始化实例对象;
线程二:可能在线程一实例化对象还未创建完成的时候进来,此刻,进行判断依然为null,然后线程二又去实例化对象了,这样下来不就出现了两个对象了,显然这不是想要的结果。
此时,就需要使用synchronied来实现线程同步,当一个线程获得锁的时候其他线程就只能在外等着
那么是不是说我们以后就不用懒汉式了呢?当然不是当我们需要在getInstance()放法着那个传入参数的时候就得用懒汉式了,饿汉也是有弊端的当我们只需要获得一个全局变量的时候你偏偏还要给我初始化一个实例
,不就导致加载的时间比较长了。
四.双重校验锁
使用synchronized看起来是解决了线程安全的问题,但是也确实大大降低了性能,同步方法要比一般方法慢很多,如果频繁调用getInstance()会造成性能的累计消耗,但是使用双重校验锁就不一样了
在同步代码块前添加一个if(d==null)的判断是因为,单例只需要被创建一次,后面执行的getInstance()方法只需要直接返回实例化对象就好了,并不需要在执行同步代码块这一部分,但是当线程1和线程2同时判断(d==null)的时候就会一起进入到同步代码块当中,如果没有你面的if(d==null)的判断就会导致创建出两个对象。
双重校验锁实现了延迟加载,也解决了线程安全的问题,也不能说这就万无一失了,因为这里还要考虑到指令重排优化的问题,假设线程1在获得锁之后判断为null开始new Demo4()的时候,JVM要做三件事:
(1)在堆中分配一块空间
(2)实行public Demo4(){}构造函数进行初始化
(3)将对象指向堆中分配好的空间
但是编译器会对流程的顺序进行优化处理,我们都知道执行完(3)的时候对象就不是null了,但是如果优化的顺序是1-3-2的话在执行(3)的时候还没有执行到(2),突然一个线程闯入,此时判断线程不为null就直接将对象返回了,如果恰巧你又需要堆某些成员变量进行初始化的话,就有可能报错了
这就是为什么使用关键字volatile来保证禁止指令重排优化了。
五.静态内部类
这种方式就是利用类加载的方式,和饿汉式很相似不过是通过静态内部类的方法来实现的,同样保证了对象的延迟加载,只要静态内部类不被调用就不会创建出对象,同时也是避免了线程不安全的问题,但是是利用了JVM的加载机制,并不使用与所有语言。
六.枚举——单例的终极模式
使用枚举除了线程安全和防止反射调用构造器之外,还提供了自动序列化机制,防止反序列化的时候创建新的对象。
参考文章:http://blog.csdn.net/goodlixueyong/article/details/51935526?locationNum=2&fps=1