每天一个设计模式-4 单例模式(Singleton)
1.实际生活的例子
有一天,你的自行车的某个螺丝钉松了,修车铺离你家比较远,而附近的五金店有卖扳手;因此,你决定去五金店买一个扳手,自己把螺丝钉固定紧。不一会儿,自行车就被你修好了;首先,这个扳手你不会扔掉,下次用的时候直接找出来就用了。好,今天的主题出来了“单例模式”。
2.与变成关联
在上面的例子中,能找出几个关键字:“买一个扳手”,“螺丝钉固定紧”,“不会扔掉扳手”,“下次用直接找出来”。我们结合标题和这几个关键字好好理解一下:
买一个扳手:创建一个实例。
螺丝钉固定紧:这个实例的功能。
不会扔掉扳手:保存这个实例。
下次用直接找出来:从保存的实例中取出来。
ok,今天我们学习的重点就是这些。
3.单例模式的定义
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
分析:“仅有一个实例”,如何做到仅有一个实例呢?我们知道,java中实例的生成需要通过构造函数,外部通过这个构造函数生成对应的对象,那么如果我们把构造函数私有化不就可以做到外部无法实例化该对象了吗;但是,该如何调用这个实例的方法呢?我们可以在该类内部执行构造函数,并将构造后的对象保存在该类的属性中,并提供一个全局的访问点供外部调用,因为不能在外部生成该类实例,所以这个全局访问点被static修饰,这样我们就可以直接通过类访问这个方法。
好的!!下面放出代码。
4.类图
UML类图讲解:http://blog.csdn.net/tianhai110/article/details/6339565
4.代码
实现单利模式,有两种方法,一种是懒汉式,另一种是饿汉式,最终都是单例模式,只是实现方式略有不同。下面先放出代码:
懒汉式实现:
public class BanShou { private static BanShou savedBanShou=null; public void finalize() throws Throwable { } private BanShou(){ System.out.println("我买了一个扳手"); } public static BanShou getInstance(){ if(savedBanShou==null){ savedBanShou = new BanShou(); return savedBanShou; } System.out.println("嘿,我找到了我以前买的扳手,这下可以不用再买了"); return savedBanShou; } public void repaire(){ System.out.println("拧紧螺丝钉"); } }
扳手类
public class Client { public static void main(String[] args) { for(int i=0;i<3;i++){ System.out.println("这是第"+i+"次用到了扳手"); BanShou.getInstance().repaire();; } } }
客户类
饿汉式实现:
public class BanShou2 { private static BanShou2 savedBanShou=new BanShou2(); public void finalize() throws Throwable { } private BanShou2(){ System.out.println("我买了一个扳手"); } public static BanShou2 getInstance(){ return savedBanShou; } public void repaire(){ System.out.println("拧紧螺丝钉"); } }
饿汉式扳手类
测试结果:
这是第0次用到了扳手
我买了一个扳手
拧紧螺丝钉
这是第1次用到了扳手
嘿,我找到了我以前买的扳手,这下可以不用再买了
拧紧螺丝钉
这是第2次用到了扳手
嘿,我找到了我以前买的扳手,这下可以不用再买了
拧紧螺丝钉
5.模式讲解
在上面的代码中看到单例模式可以用两种方式实现,那么他们的区别是什么呢?首先,先从名称上理解一下,懒汉式:因为很懒,所以做事都是拖到最后,不得不做时才去做;饿汉式:因为很饿,所以做事很急。体会一下应该就能懂了。↓↓↓↓懒汉式
public static BanShou getInstance(){ if(savedBanShou==null){ savedBanShou = new BanShou(); return savedBanShou; } System.out.println("嘿,我找到了我以前买的扳手,这下可以不用再买了"); return savedBanShou; }
获取实例时,先去判断是否为空,如果为空,再去创建(拖到最后,不得不做时才去做)。
↓↓↓↓饿汉式
private static BanShou2 savedBanShou=new BanShou2();
public static BanShou2 getInstance(){ return savedBanShou; }
获取实例前就已经创建好了,无论你用不用(做事很急)。
在懒汉式实现方式中也涉及了延迟加载的思想(LazyLoad),通俗的说就是:一开始时不要加载资源或数据,一直等,等到马上就要使用这个资源或者数据了,躲不过了才去加载,这在实际开发中是一种常见的思想,尽可能的节约资源。
饿汉式和懒汉式的实现还体现了缓存的思想:当某个文件,或数据库数据被频繁使用,如果每次都从文件或数据库中读取,速度会变得很慢,如果把这些数据放到内存中,每次取数据时都从缓存中提取,那么速度便会得到很大的提升。
private static BanShou2 savedBanShou=new BanShou2();
private static BanShou savedBanShou=null;
这两行代码就是起到了缓存的作用。
6.单利模式的线程问题
懒汉式实现方式是线程不安全的,比如:
如何解决呢?想让懒汉式变成线程安全的,最简单的办法就是用synchronized
public static synchronized BanShou getInstance()
但这样做会降低系统性能,因为线程互斥,每次都要等待。
有一种双重加锁方式可以解决这个问题。双重加锁方式就是:
并不是每次进入getInstance方法都需要同步,而是先不同步,进入方法过后,先检查实例是否存在,如果不存在才进入下面的同步块,这是第一重检查。进入同步块过后,再次检查实例是否存在,如果不存在,就在同步的情况下创建实例,这是第二重检查。这样,就只需要同步一次,从而减少了多次在同步情况下进行判断所浪费的时间。双重加锁机制的实现会使用一个关键字volatile,它的意思是:被volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。
但这种方式用到了volatile,它会屏蔽掉虚拟机中的一些必要的代码优化,所以运行效率不是很高,下面推荐一个更好的单例实现方式:
直接放出源代码吧,也涉及一些其他的基础知识,这里就不写了,毕竟这么晚了;今天比较忙,所以现在才更新。不好意了。
更好的单例模式实现源代码:
public class Singleton { //这部分只有被调用到时,才会被装载,既线程安全,有延迟加载 private static class SingletonHolder{ private static Singleton instance = new Singleton(); } private Singleton(){ } public static Singleton getInstance(){ return SingletonHolder.instance; } }
7.总结
实现单例模式,就必须将构造器私有化,在类内部实例化,并提供全局的访问点,从而达到控制实例的数目的目的
-------博主写博客不容易,转载请注明出处,谢谢:http://www.cnblogs.com/xiemubg/p/5954949.html