我们知道类可以用来实例化对象,而对象可以被实例化多个,但是有些时候我们并不想实例化多个对象,只希望只有一个,例如做一个窗体应用程序,有一个按钮,点击一下可以出现菜单,再点击一下就不会弹出同样的一个菜单,比如Word中我们多次点击替换按钮的时候只会出现一个窗口。
这样的只允许出现一个实体的设计模式,被形象地称为单例模式,单例即单个实例。
单例模式的定义如下:
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
——《大话设计模式》
它的UML类图十分简单
由图可知,在只允许出现一个实例的类中,维护着一个私有静态类实例对象,这个类的构造方法也是私有的,只对外提供一个静态的GetInstance()方法。所以,这个类的对象实例,只能通过GetInstance()来获取,在GetInstan()中,将会判断这个类是否被实例化,如果被实例化,将返回instance对象,如果没有,则实例化instance并将其返回。
下面的代码演示了最原始的单例模式,其中,ToolBox类只允许出现一次。
以下所有代码基于Java语言编写。
class ToolBox
{
private static ToolBox instance;
private ToolBox(){}
public static ToolBox getInstance()
{
if(instance == null)
{
System.out.println("初始化了。。。");
instance = new ToolBox();
}
return instance;
}
}
public class Main
{
public static void main(String[] args)
{
ToolBox tb = ToolBox.getInstance();
ToolBox tb1 = ToolBox.getInstance();
}
}
运行结果如下:
显然,21、22行在两次尝试获得ToolBox对象的时候,该类只被实例化了一次。
最后我们不得不考虑一下多线程问题,如果不考虑的话,还是可能会创建多个实例的。或许我们会这么写
class ToolBox
{
private static ToolBox instance;
private ToolBox(){}
public static synchronized ToolBox getInstance()
{
if(instance == null)
{
System.out.println("初始化了。。。");
instance = new ToolBox();
}
else
{
System.out.println("对象存在。。。");
}
return instance;
}
}
class ThreadHome implements Runnable
{
public void run()
{
System.out.println("当前线程:"+Thread.currentThread().getName()+"访问");
ToolBox tb = ToolBox.getInstance();
}
}
public class Main
{
public static void main(String[] args)
{
ThreadHome th = new ThreadHome();
Thread t1 = new Thread(th,"线程1");
Thread t2 = new Thread(th,"线程2");
t1.start();
t2.start();
}
}
虽然上面使用了同步方法,多个线程竞争时只允许一个线程进入getInstance(),但是试想,计算机的CPU是为每一个线程分配时间片的,如果在规定的时间片内没有运行完成,则会进入就绪状态,等待再次获取时间片,假设同时有两个线程尝试获取ToolBox类,我们就暂定为线程1和线程2吧。线程1在执行到instance = new ToolBox();
前失去CPU时间,而线程2完整运行了getInstance(),此时,线程1被唤醒,执行完getInstance(),这样,系统中就会存在两个ToolBox对象了。
上面这种方式还有一个弊端就是线程每次构造对象都需要加同步锁,这将从某个方面降低系统性能,所以我们不妨做一个改进,就对构造对象的语句加同步锁,在构造对象之前,再次进行一次instance是否为空的判断,这样就可以防止上面所分析的多线程问题。这种方式叫做双重锁定,同时也被叫做懒汉式单例类,与之对应的叫饿汉式单例类,不同之处在于,饿汉式单例类是利用静态初始化的方式在自己加载时就将自己实例化,懒汉式单例类是在第一次被引用时才会使用,比较懒嘛,等要用的时候才动身。
上面代码修改如下:
class ToolBox
{
private static ToolBox instance;
private ToolBox(){}
public static ToolBox getInstance()
{
if(instance == null)
{
synchronized(ToolBox.class)
{
if(instance == null)
{
System.out.println("初始化了。。。");
instance = new ToolBox();
}
}
}
else
{
System.out.println("对象存在。。。");
}
return instance;
}
}
class ThreadHome implements Runnable
{
public void run()
{
System.out.println("当前线程:"+Thread.currentThread().getName()+"访问");
ToolBox tb = ToolBox.getInstance();
}
}
public class Main
{
public static void main(String[] args)
{
ThreadHome th = new ThreadHome();
Thread t1 = new Thread(th,"线程1");
Thread t2 = new Thread(th,"线程2");
t1.start();
t2.start();
}
}
运行结果如下:
参考资料:
浅淡java单例模式结合多线程测试 –博客园