让我们从一段对话开始我们今天所讲的话题。
老公:老婆,你们家就你一个吗?
老婆:找打啊你,我们家我有没有兄弟姐妹你都不知道啊,晚上跪键盘去,除了我,还有两个姐姐,两个哥哥,还有个宝贝弟弟呢。
老公:哇,那么多啊,那岂不是小时候在一起玩疯了你们,肯定小时候你没少调皮让你爸妈省心。
老婆:对的呢,不过像你这样家里只有一个独生子的,小时候多没劲啊,孤独寂寞吧哈哈。
老公:虽然没人玩,但是家里的玩具都归我管,想吃什么就吃什么,没人跟我抢,所以只有一个独生子也是不错的呢嘿嘿。
.............
闲话不多说,从上面的对话我们知道,家里只有一个孩子也是有其好处的嘛,在软件设计中,我们有时也会需要将某个对象只生成一个,因为太多可能会造成混乱,不利于管理,例如我们的Windows任务管理器,Java类加载器,注册表等,都是指有一个,那么该怎么设计实现呢?这就是本人需要讲述的问题。
为了解决上面的问题,单态模式应运而生,作为设计模式中最简单的设计模式,我想今天我们就首先来看看它到底是什么?
首先,我问大家一个问题,如果让你设计一个类,这个类只能生成一个对象,大家会怎么设计呢?想想先.......
肯定不能有公有的构造函数,否则外部就可以通过new操作创建出很多对象,于是我们想到把类的构造函数设置为private权限,新的问题又来了,既然是private,那么如何去创建对象呢?既然我们无法通过new创建得到,那么我们就只有通过类的静态方法来获得这个对象了,当然我们需要在类中定义一个该类对象的引用,而且这个引用应该是私有的静态的。如下所示:
public class Singleton { private static Singleton instance=null; private Singleton(){} public static Singleton getInstance() { if(instance==null) instance=new Singleton(); return instance; } }
其结构图如下所示:
这就是单态模式的一个简单实现(当然这种写法还是有点不足的,在后满我们将进行详细讲述),我们可以看到,单态模式中包含三个主要部分,一个是私有的对象,私有的构造函数,和公有的静态获取函数(用于得到对象的引用)。
这里我们给出单态模式的定义:单态模式确保每个类只有一个实例,并且提供为这个私有对象提供一个全局的访问点。
刚才提到了上面的程序实际上是有问题的,现在我们就来讨论一下。
你是否学习过多线程编程?如果没有学过,那么先看看这个多线程的资料吧或者你应该听说过这个吧,在多线程编程模式下,上面实现的单态模式是有问题的,请看下图:
所以,我们需要对上述代码进行改进。下面给出三种解决办法,大家可以根据自己的情况和实际项目的要求进行选择使用。
(1)对共有的外部进口进行同步化。即将方法用synchronized关键字修饰,如下所示:
public static synchronized Singleton getInstance() { if(instance==null){instance=new Singleton(); return instance; }
这样,当一个线程进入方法以后,其他线程只有等到该线程退出该方法才能进入。由于在每次调用该方法时,都会进行同步处理,对程序执行效率有一定影响,一般同步一个方法可能会造成程序执行效率下降100倍,所以,如果使用比较频繁,请不要使用该方法。
(2)采用在类加载时直接创建对象,如下所示:
public class Singleton { private static Singleton instance=new Singleton(); private Singleton(){} public static synchronized Singleton getInstance() { return instance; } }
这种方法使得JVM在加载这个类时会创建唯一的对象,并在以后访问该对象时指向的都是同一个。这种方法的缺点在于,如果这个类的初始化需要做大量的工作,而后这个类在之后并没有被调用,则会造成较大的资源浪费。
(3)使用双重检测加锁,如下所示:
public class Singleton { private voilatile static Singleton instance=null; private Singleton(){} public static synchronized Singleton getInstance() { if(instance==null) { synchronized(Singleton.class) { if(instance==null) { instance=new Singleton(); } } } return instance; }
使用该方法,首先检查该实例是否已经创建好,如果未创建,才能同步,这样一来,只会在第一次创建的时候可能出现同步,降低了同步造成的性能损失。
到此为止我们对单态模式有了详细的认识了。